At
Data Geekery, we love Java. And as we’re really into
jOOQ’s fluent API and query DSL, we’re absolutely thrilled about what Java 8 will bring to our ecosystem. We have
blogged a couple of times about some nice Java 8 goodies, and now we feel it’s time to start a new blog series, the…
Java 8 Friday
Every Friday, we’re showing you a couple of nice new tutorial-style Java 8 features, which take advantage of lambda expressions, extension methods, and other great stuff.
You’ll find the source code on GitHub.
Java 8 Goodie: Lambdas and Sorting
Sorting arrays, and collections is an awesome use-case for Java 8’s lambda expression for the simple reason that
Comparator
has always been a
@FunctionalInterface all along since its introduction in JDK 1.2. We can now supply
Comparators
in the form of a lambda expression to various
sort()
methods.
For the following examples, we’re going to use this simple
Person
class:
static class Person {
final String firstName;
final String lastName;
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return "Person{" +
"firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
'}';
}
}
Obviously, we could add natural sorting to
Person
as well by letting it implement
Comparable
, but lets focus on external
Comparators
. Consider the following list of
Person
, whose names are generated with some online random name generator:
List<Person> people =
Arrays.asList(
new Person("Jane", "Henderson"),
new Person("Michael", "White"),
new Person("Henry", "Brighton"),
new Person("Hannah", "Plowman"),
new Person("William", "Henderson")
);
We probably want to sort them by last name and then by first name.
Sorting with Java 7
A “classic” Java 7 example of such a
Comparator
is this:
people.sort(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
int result = o1.lastName.compareTo(o2.lastName);
if (result == 0)
result = o1.firstName.compareTo(o2.firstName);
return result;
}
});
people.forEach(System.out::println);
And the above would yield:
Person{firstName='Henry', lastName='Brighton'}
Person{firstName='Jane', lastName='Henderson'}
Person{firstName='William', lastName='Henderson'}
Person{firstName='Hannah', lastName='Plowman'}
Person{firstName='Michael', lastName='White'}
Sorting with Java 8
Now, let’s translate the above to equivalent Java 8 code:
Comparator<Person> c = (p, o) ->
p.lastName.compareTo(o.lastName);
c = c.thenComparing((p, o) ->
p.firstName.compareTo(o.firstName));
people.sort(c);
people.forEach(System.out::println);
The result is obviously the same. How to read the above? First, we assign a lambda expression to a local
Person Comparator
variable:
Comparator<Person> c = (p, o) ->
p.lastName.compareTo(o.lastName);
Unlike
Scala, C#, or Ceylon which know type inference from an expression towards a local variable declaration through a
val
keyword (or similar), Java performs type inference from a variable (or parameter, member) declaration towards an expression that is being assigned.
In other, more informal words, type inference is performed from “left to right”, not from “right to left”. This makes chaining
Comparators
a bit cumbersome, as the Java compiler
cannot delay type inference for lambda expressions until you pass the comparator to the
sort()
method.
Once we have assigned a
Comparator
to a variable, however, we can fluently chain other comparators through
thenComparing()
:
c = c.thenComparing((p, o) ->
p.firstName.compareTo(o.firstName));
And finally, we pass it to the
List
‘s new
sort()
method, which is a default method implemented directly on the
List
interface:
default void sort(Comparator<? super E> c) {
Collections.sort(this, c);
}
Workaround for the above limitation
While Java’s type inference “limitations” can turn out to be a bit frustrating, we can work around type inference by creating a generic
IdentityComparator
:
class Utils {
static <E> Comparator<E> compare() {
return (e1, e2) -> 0;
}
}
With the above
compare()
method, we can write the following fluent comparator chain:
people.sort(
Utils.<Person>compare()
.thenComparing((p, o) ->
p.lastName.compareTo(o.lastName))
.thenComparing((p, o) ->
p.firstName.compareTo(o.firstName))
);
people.forEach(System.out::println);
Extracting keys
This can get even better. Since we’re usually comparing the same POJO / DTO value from both
Comparator
arguments, we can provide them to the new APIs through a “key extractor” function. This is how it works:
people.sort(Utils.<Person>compare()
.thenComparing(p -> p.lastName)
.thenComparing(p -> p.firstName));
people.forEach(System.out::println);
So, given a
Person p
we provide the API with a function extracting, for instance,
p.lastName
. And in fact, once we use key extractors, we can omit our own utility method, as the libraries also have a
comparing()
method to initiate the whole chain:
people.sort(
Comparator.comparing((Person p) -> p.lastName)
.thenComparing(p -> p.firstName));
people.forEach(System.out::println);
Again, we need to help the compiler as it cannot infer all types, even if in principle, the
sort()
method would provide enough information in this case.
To learn more about Java 8’s generalized type inference, see our previous blog post.
Conclusion
As with Java 5, the biggest improvements of the upgrade can be seen in the JDK libraries. When Java 5 brought typesafety to
Comparators
, Java 8 makes them easy to read and write (give or take the odd type inference quirk).
Java 8 is going to revolutionise the way we program, and next week, we will see how Java 8 impacts the way we interact with SQL.
More on Java 8
In the mean time, have a look at
Eugen Paraschiv’s awesome Java 8 resources pageLike this:
Like Loading...
If the Person class has getter methods, the last example can also be written using method references:
Yes, nice thinking. Method references make for very useful “key extractors” when used with those
comparing
methods