What is a Comparator?

A Comparator of type T is something that takes two objects of type T and returns a negative integer, zero, or a positive integer if the first argument is less than, equal to, or greater than the second when its compare method is called. Let us take a closer look at an example where we have a Comparator<String> that we want to compare two strings using their natural order:

    Comparator<String> naturalOrder = (String first, String second) -> first.compareTo(second);
    Stream.of("Snail", "Ape", "Bird", "Ant", "Alligator")
        .sorted(naturalOrder)
        .forEachOrdered(System.out::println);

This will print out all animals in alphabetical order: Alligator, Ant, Ape, Bird and Snail because the sorted operator will sort the elements in the stream according to the provided Comparator.

In Speedment, the concept of a Field is of central importance. Fields can be used to produce Comparators that are related to the field.

Here is an example of how a StringField can be used in conjuction with a Film object:

    Comparator<Film> title = Film.TITLE.comparator();

    films.stream()
        .sorted(title)
        .forEachOrdered(System.out::println);

In this example, the StringField’s method Film.TITLE::comparator returns a Comparator<Film> that, when comparing two Film objects, will return a negative value if the title of the first Film is less than the name of the second Film, zero if the title of the Film objects are equal, a positive value if the title of the first Film is greater than the title of the second Film.

When run, the code above will produce the following output:

FilmImpl { ..., title = ACADEMY DINOSAUR, ...
FilmImpl { ..., title = ACE GOLDFINGER, ...
FilmImpl { ..., title = ADAPTATION HOLES, ...
...

and will be rendered to the following SQL query (for MySQL):

SELECT 
    `film_id`,`title`,`description`,`release_year`,
    `language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
    `length`,`replacement_cost`,`rating`,`special_features`,`last_update` 
FROM 
    `sakila`.`film` 
ORDER BY 
    `sakila`.`film`.`title` ASC

It would be possible to express the same semantics using Comparator.comparing and a method reference:

    films.stream()
        .sorted(Comparator.comparing(Film::getTitle))
        .forEachOrdered(System.out::println);

but Speedment would not be able to recognize and optimize vanilla comparators. Because of this, developers are highly encouraged to use the provided Fields when obtaining comparators because these comparators, when used, can be recognizable by the Speedment query optimizer.

The rest of this chapter will be about how we can get comparators from different Field types and how these comparators can be used.

Comparators

Comparators are only available from fields that represents comparable values like int, Integer and String. Fields like Boolean cannot be compared because they have no order defined.

The following methods are available to a ComparableField that is always associated to a Comparable field (e.g. Integer, String, Date, Time etc.). Comparable fields can be tested for equality and can also be compared to other objects of the same type. In the table below, the “Outcome” is a stream where the elements are sorted() using a Comparator<ENTITY> and they will have the:

Method Outcome Example
comparator() natural order with nulls last A, B, null
comparator().reversed() reversed (natural order with nulls last) null, B, A
comparatorNullFieldsFirst() natural order with nulls first null, A, B
comparatorNullFieldsFirst().reversed() reversed (natural order will nulls first) B, A, null

comparator

The following example shows a solution where we print out the films sorted by title:

    films.stream()
        .sorted(Film.TITLE.comparator())
        .forEachOrdered(System.out::println);

The code will produce the following output:

FilmImpl { ..., title = ACADEMY DINOSAUR, ...
FilmImpl { ..., title = ACE GOLDFINGER, ...
FilmImpl { ..., title = ADAPTATION HOLES, ...
...

and will be rendered to the following SQL query (for MySQL):

SELECT 
    `film_id`,`title`,`description`,`release_year`,
   `language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
   `length`,`replacement_cost`,`rating`,`special_features`,`last_update`
FROM 
    `sakila`.`film` 
ORDER BY
     `sakila`.`address`.`address2` is null,
     `sakila`.`film`.`title` ASC

comparator reversed

The following example shows a solution where we print out the films sorted by title in reversed order:

    films.stream()
        .sorted( Film.TITLE.comparator())
        .forEachOrdered(System.out::println);

The code will produce the following output:

FilmImpl { ..., title = ZORRO ARK, ...
FilmImpl { ..., title = ZOOLANDER FICTION, ...
FilmImpl { ..., title = ZHIVAGO CORE, ...
...

and will be rendered to the following SQL query (for MySQL):

SELECT 
    `film_id`,`title`,`description`,`release_year`,
   `language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
   `length`,`replacement_cost`,`rating`,`special_features`,`last_update`
FROM 
    `sakila`.`film` 
ORDER BY
    `sakila`.`address`.`address2` is not null,
    `sakila`.`film`.`title` DESC

comparatorNullFieldsFirst

The following example shows a solution where we print out the addresses sorted by the address2 field (which is nullable) with null values first:

    addresses.stream()
        .sorted(Address.ADDRESS2.comparatorNullFieldsFirst())
        .forEachOrdered(System.out::println);

The code will produce the following output:

AddressImpl { addressId = 1, address = 47 MySakila Drive, address2 = null, ...
AddressImpl { addressId = 2, address = 28 MySQL Boulevard, address2 = null, ...
AddressImpl { addressId = 3, address = 23 Workhaven Lane, address2 = null, ...
AddressImpl { addressId = 4, address = 1411 Lillydale Drive, address2 = null, ...
AddressImpl { addressId = 256, address = 1497 Yuzhou Drive, address2 = , ...
AddressImpl { addressId = 512, address = 1269 Ipoh Avenue, address2 = , ...
...

and will be rendered to the following SQL query (for MySQL):

SELECT 
    `address_id`,`address`,`address2`,`district`,
    `city_id`,`postal_code`,`phone`,`location`,`last_update` 
FROM 
    `sakila`.`address` 
ORDER BY 
    `sakila`.`address`.`address2` is not null,
    `sakila`.`address`.`address2` ASC

comparatorNullFieldsFirst reversed

The following example shows a solution where we print out the addresses sorted by the address2 field (which is nullable) with null values first but reversed:

    addresses.stream()
        .sorted(Address.ADDRESS2.comparatorNullFieldsFirst().reversed())
        .forEachOrdered(System.out::println);

The code will produce the following output:

AddressImpl { addressId = 256, address = 1497 Yuzhou Drive, address2 = , ...
AddressImpl { addressId = 512, address = 1269 Ipoh Avenue, address2 = , ...
...
AddressImpl { addressId = 1, address = 47 MySakila Drive, address2 = null, ...
AddressImpl { addressId = 2, address = 28 MySQL Boulevard, address2 = null, ...
...

and will be rendered to the following SQL query (for MySQL):

SELECT 
    `address_id`,`address`,`address2`,`district`,
    `city_id`,`postal_code`,`phone`,`location`,`last_update` 
FROM 
    `sakila`.`address` 
ORDER BY 
    `sakila`.`address`.`address2` is null,
    `sakila`.`address`.`address2` DESC

Reversing Comparators

All comparators (including already reversed comparators) can be reversed by calling the reversed() method. Reversion means that the result of the Comparator will be inverted (i.e. negative values become positive and positive values become negative). Here is a list of comparators and their corresponding reversion:

Primitive Comparators

For performance reasons, there are a number of primitive field types available in addition to the reference field type. By using a primitive field, unnecessary boxing and auto-boxing can be avoided. Primitive fields also generates primitive comparators like IntFieldComparator or LongFieldComparator

The following primitive types and their corresponding field types are supported by Speedment:

Primitive Type Primitive Field Type Comparators
byte ByteField ByteFieldComparator
short ShortField ShortFieldComparator
int IntField IntFieldComparator
long LongField LongFieldComparator
float FloatField FloatFieldComparator
double DoubleField DoubleFieldComparator
char CharField CharFieldComparator

This is something that is handled automatically by Speedment under the hood and does not require any additional coding. Our code will simply run faster width these specializations.

Combining Comparators

Several comparators can be combined to form a composite comparator that will sort entities using a combination of sort keys with different priorities.

The following example shows a solution where we print out the films sorted firstly by rating in reversed order and then secondly (if the rating is the same) we sort by title:

    films.stream()
        .sorted(
            Film.RATING.comparator().reversed()
                .thenComparing(Film.TITLE.comparator())
        )
        .forEachOrdered(System.out::println);

The code will produce the following output:

FilmImpl { filmId = 3, title = ADAPTATION HOLES, ..., rating = NC-17, ...
FilmImpl { filmId = 10, title = ALADDIN CALENDAR, ..., rating = NC-17, specialFeatures = Trailers,Deleted Scenes, lastUpdate = 2006-02-15 05:03:42.0 }
FilmImpl { filmId = 14, title = ALICE FANTASIA, ..., rating = NC-17, ...
...

and will be rendered to the following SQL query (for MySQL):

SELECT 
    `film_id`,`title`,`description`,`release_year`,
   `language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
   `length`,`replacement_cost`,`rating`,`special_features`,`last_update`
FROM 
    `sakila`.`film` 
ORDER BY
    `sakila`.`film`.`rating`IS NOT NULL,
    `sakila`.`film`.`rating` DESC,
    `sakila`.`film`.`title` ASC

Examples

The following example shows another solution where we print out the films sorted firstly by rating in reversed order and then secondly (if the rating is the same) we sort by title:

    films.stream()
        .sorted(Film.TITLE.comparator())              // <-- Second order
        .sorted(Film.RATING.comparator().reversed())  // <-- First order
        .forEachOrdered(System.out::println);

The code will produce the following output:

FilmImpl { filmId = 3, title = ADAPTATION HOLES, ..., rating = NC-17, ...
FilmImpl { filmId = 10, title = ALADDIN CALENDAR, ..., rating = NC-17, specialFeatures = Trailers,Deleted Scenes, lastUpdate = 2006-02-15 05:03:42.0 }
FilmImpl { filmId = 14, title = ALICE FANTASIA, ..., rating = NC-17, ...
...

and will be rendered to the following SQL query (for MySQL):

SELECT 
    `film_id`,`title`,`description`,`release_year`,
   `language_id`,`original_language_id`,`rental_duration`,`rental_rate`,
   `length`,`replacement_cost`,`rating`,`special_features`,`last_update`
FROM 
    `sakila`.`film` 
ORDER BY
    `sakila`.`film`.`rating`IS NOT NULL, 
    `sakila`.`film`.`rating` DESC, 
    `sakila`.`film`.`title` ASC    

Discussion

Join the discussion in the comment field below or on Gitter