Consider the following example where we are sorting people based on their last name:
public class ComparatorsExample {
public static class Person {
private String lastName;
public Person(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
@Override
public String toString() {
return "Person: " + lastName;
}
}
public static void main(String[] args) {
Person p1 = new Person("Jackson");
Person p2 = new Person("Stackoverflowed");
Person p3 = new Person(null);
List<Person> persons = Arrays.asList(p3, p2, p1);
persons.sort(Comparator.comparing(Person::getLastName));
}
}
Now, let's assume that getLastName
returns an optional:
public Optional<String> getLastName() {
return Optional.ofNullable(lastName);
}
Obviously persons.sort(Comparator.comparing(Person::getLastName));
will not compile since Optional
(the type getLastName
returns) is not a comparable. However, the value it holds is.
The first google search points us in this answer. Based on this answer we can sort persons by doing:
List<Person> persons = Arrays.asList(p3, p2, p1);
OptionalComparator<String> absentLastString = absentLastComparator(); //type unsafe
persons.sort((r1, r2) -> absentLastString.compare(r1.getLastName(), r2.getLastName()));
My question is, is it possible to have this kind of sorting using a function (key extractor) just like Comparator.comparing?
I mean something like (without caring absent values first or last):
persons.sort(OptionalComparator.comparing(Person::getLastName));
If we look into Comparator.comparing, we see the following code:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable) (c1, c2) -> {
return keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
};
}
I tried multiple ways to make it return an OptionalComparator
instead of a simple Comparator
, but everything I tried and made sense to me was unable to be compiled. Is it even possible to achieve something like that? I guess type-safety cannot be achieved, since even Oracle's comparing
throws a type-safety warning.
I am on Java 8.
You can use Comparator#comparing(Function,Comparator)
:
Accepts a function that extracts a sort key from a type
T
, and returns aComparator<T>
that compares by that sort key using the specifiedComparator
.
Here's an example based on the code in your question:
persons.sort(comparing(Person::getLastName, comparing(Optional::get)));
Basically this is using nested key extractors to ultimately compare the String
objects representing the last names. Note this will cause a NoSuchElementException
to be thrown if either Optional
is empty. You can create a more complicated Comparator
to handle empty Optional
s1:
// sort empty Optionals last
Comparator<Person> comp =
comparing(
Person::getLastName,
comparing(opt -> opt.orElse(null), nullsLast(naturalOrder())));
persons.sort(comp);
If you need to do this a lot then consider creating utility methods in a manner similar to Comparator#nullsFirst(Comparator)
and Comparator#nullsLast(Comparator)
1:
// empty first, then sort by natural order of the value
public static <T extends Comparable<? super T>> Comparator<Optional<T>> emptyFirst() {
return emptyFirst(Comparator.naturalOrder());
}
// empty first, then sort by the value as described by the given
// Comparator, where passing 'null' means all non-empty Optionals are equal
public static <T> Comparator<Optional<T>> emptyFirst(Comparator<? super T> comparator) {
return Comparator.comparing(opt -> opt.orElse(null), Comparator.nullsFirst(comparator));
}
// empty last, then sort by natural order of the value
public static <T extends Comparable<? super T>> Comparator<Optional<T>> emptyLast() {
return emptyLast(Comparator.naturalOrder());
}
// empty last, then sort by the value as described by the given
// Comparator, where passing 'null' means all non-empty Optionals are equal
public static <T> Comparator<Optional<T>> emptyLast(Comparator<? super T> comparator) {
return Comparator.comparing(opt -> opt.orElse(null), Comparator.nullsLast(comparator));
}
Which can then be used like:
persons.sort(comparing(Person::getLastName, emptyLast()));
1. Example code simplified based on suggestions provided by @Holger. Take a look at the edit history to see what the code looked like before, if curious.
Slaw's answer helped a lot and based on his answer I got exactly what I wanted. However, as he mentions
persons.sort(comparing(Person::getLastName, comparing(Optional::get));
will throw exception if a value of an Optional
is not present. By that, we are missing most of the OptionalComparator
point-sense. But luckily it can be converted to:
persons.sort(comparing(Person::getLastName, absentFirstComparator()));
where the case of a not present value will be handled.
Also, these two methods can be created:
public static <T, U extends Optional> Comparator<T> absentFirst(Function<? super T, ? extends U> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T>) (c1, c2) -> absentFirstComparator().compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
public static <T, U extends Optional> Comparator<T> absentLast(Function<? super T, ? extends U> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T>) (c1, c2) -> absentLastComparator().compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
and finally I get exactly what I want by:
persons.sort(absentLast(Person::getLastName));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With