Comparing with nullsFirst and nullsLast

Sorting in Java is easy:

public class Data {
    private final String value;
    public Data(String value) {
        this.value = value;
    }
 
    public String getValue() { return value; }
 
    @Override public String toString() {
        return String.format("Data(%s)", this.value);
    }
}
 
public static void main(String[] args) {
   List<Data> listOfData = Arrays.asList(
          new Data("foo"),
          new Data("bar"),
          new Data("nyu"));
 
   listOfData.sort(comparing(Data::getValue));
   listOfData.forEach(System.out::println);
}
//OUTPUT:
// Data(bar)
// Data(foo)
// Data(nyu)

…unless we try to sort a collection containing null values:

List<Data> listOfData = Arrays.asList(
       new Data("foo"),
       null,
       new Data("bar"),
       new Data("nyu"));
 
listOfData.sort(comparing(Data::getValue));
listOfData.forEach(System.out::println);
//OUTPUT:
// Exception in thread "main" java.lang.NullPointerException
//    at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)

Fortunately there is easy solution to this problem. But first we must decide whenever we want nulls to be first or last in sorted collection. After we made our mind we may use nifty nullsFirst or nullsLast decorators provided by Comparator interface:

import static java.util.Comparator.*;
 
List<Data> listOfData = Arrays.asList(
       new Data("foo"),
       null,
       new Data("bar"),
       new Data("nyu"));
 
listOfData.sort(nullsFirst(comparing(Data::getValue)));
listOfData.forEach(System.out::println);
//OUTPUT:
// null
// Data(bar)
// Data(foo)
// Data(nyu)

nullsFirst is great example of decorator design pattern (it adds functionality but doesn’t change interface). nullsFirst works by wrapping provided comparator in code similar to:

public static <T> Comparator<T> nullsFirst(Comparator<T> comparator) {
  return (a, b) -> {
    if (a == null)
        return (b == null) ? 0 : -1;
 
    if (b == null)
      return 1;
 
    // a and b are not null here
    return comparator.compare(a, b);
  };
}

Previous example works great unless we try to sort a collection containing Data(null):

List<Data> listOfData = Arrays.asList(
       new Data("foo"),
       new Data(null),
       new Data("bar"),
       new Data("nyu"));

listOfData.sort(nullsFirst(comparing(Data::getValue)));
listOfData.forEach(System.out::println);
//OUTPUT:
// Exception in thread "main" java.lang.NullPointerException
//  at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
//  at java.util.Comparators$NullComparator.compare(Comparators.java:83)

But do not despair nullsFirst can help us again:

listOfData.sort(nullsFirst(
    comparing(Data::getValue, nullsFirst(naturalOrder()))));

listOfData.forEach(System.out::println);
//OUTPUT:
// Data(null)
// Data(bar)
// Data(foo)
// Data(nyu)

Ta da! It works but readability suffers greatly… You may ask what is this thing:

comparing(Data::getValue, nullsFirst(naturalOrder()))

First: we use the following overload of comparing method:

public static <T, U> Comparator<T> comparing(
   Function<? super T, ? extends U> keyExtractor,
   Comparator<? super U>            keyComparator)
{
    return (c1, c2) -> keyComparator.compare(
          keyExtractor.apply(c1),
          keyExtractor.apply(c2));
}

Second: in our example nullsFirst(naturalOrder()) is a comparator that can compare nullable Strings:

Comparator<String> cmp = nullsFirst(naturalOrder());
cmp.compare("foo", "zzz"); // -1
cmp.compare("foo", null);  // 1

Now everything should be clear (I hope).

To sum up in this post we get to know two little methods nullsFirst and nullsLast. I admit that they are a bit unintuitive to use, but definitely worth to bear in mind.

marcin-chwedczuk

A Programmer, A Geek, A Human