Comparator.nullsLast with Comparator.comparingInt

nimo23 :

I have a comparator comparing Integer (which can be null), so

I tried to use Comparator.comparingInt, but it does not work:

public static final Comparator<Task> sort = 
Comparator.comparingInt(x -> x.getNumber(), 
                        Comparator.nullsLast(null));

I get the compiler error:

The method comparingInt(ToIntFunction<? super T>) 
in the type Comparator is not applicable for 
the arguments ((<no type> x) -> {}, Comparator.nullsLast(null))

When using comparing instead of comparingInt, it works:

   public static final Comparator<Task> sort = 
   Comparator.comparing(x -> x.getNumber(), 
                       Comparator.nullsLast(null));

But I am wondering why because I wanted to compare Integers so compareInt would be the right choice.

How can I use Comparator.nullsLast in combination with Comparator.comparingInt?.

rzwitserloot :
  1. Integer and int are not the same. In particular, any attempt to compare ints is incapable of dealing with null, which you do need here, so it's right out. Use comparing.

  2. Your first snippet does not work because you are passing 2 args to the comparingInt method, which only takes one: A lambda that turns a given Task into an int. This is not how you use nullsLast.

  3. Your second snippet does work, but it does something completely different: It first extracts an Integer from your Task, and then implements the sorting by using the nullsLast comparator. Which means that nulls will be last, yes, and all your other integers will be in arbitrary order. As I said, this is not how you use nullsLast.

This is how you use nullsLast:

case: Your list of tasks contains null instead of a task:

someListOfTasks.sort(Comparator.nullsLast(Comparator.comparing(task::getNumber)));

case: Your list of tasks contains a task with a null for a number:

someListOfTasks.sort(Comparator.comparing(task::getNumber, Comparator.nullsLast(Comparator.naturalOrder())));

(I think this is what you want)

case: Your list of tasks contains both nulls instead of tasks, and some tasks with null for a number:

someListOfTasks.sort(Comparator.nullsLast(Comparator.comparing(task::getNumber, Comparator.nullsLast(Comparator.naturalOrder()))));

This may feel rather wordy, but note that the one null has no relation to the other, and you may well want null task entries to sort first, whereas non-null tasks with null numbers to sort last. Nothing in your question suggests that you have null tasks in your list, so you need the second example.

So what does it mean?

Comparator.comparing(valueExtractor, valueComparator) is the general syntax. You may omit the valueComparator part, in which case you get the so-called 'natural ordering'. For Integer, it's 0,1,2,3,4.... – the obvious one. For random objects, if they implement Comparable (which java.lang.Integer does), it's however the compareTo method of the objects you're trying to sort is implemented.

Note that by definition, the natural order cannot deal with nulls (after all, trying to call compareTo on a null does the same thing calling any instance method on any null ref does: It throws NullPointerException). You can 'fix' that by not using the natural order, but by using a nullsFirst or nullsLast comparator instead. Those 'wrap' a comparator, hence the syntax is:

Comparator.nullsLast(comparatorToWrap)

and this means: First, sort all null values to the very end. Then, to compare all the non-null entries, use comparatorToWrap. In the snippet you posted, you've passed in null for comparatorToWrap here. The spec of nullsLast explicitly states: This means all non-null values are considered equal, which means, if sorting lists, arbitrary order, and for maps/sets, only 1 non-null key: Either way, not what you wanted. You clearly want for tasks with non-null numbers to sort according to the natural order of integers, i.e. a task with number '1' should sort higher than a task with number '2'. So, pass in the natural order comparator: Comparator.nullsLast(Comparator.naturalOrder()) – this means: First sort all nulls to the end, then sort the rest according to whatever's natural here.

You then want to apply this concept ('sort nulls last, the rest in natural order') not to the tasks themselves, but to whatever task.getNumber() returns, hence why we first need a lambda that extracts (so, Task::getNumber or x -> x.getNumber(); these mean the same thing), and then we pass in our Integer comparator to boot:

Comparator<Integer> sortIntegersNaturallyWithNullsLast = Comparator.nullsLast(Comparator.naturalOrder());

Function<Task, Integer> extractNumber = Task::getNumber;

listOfTasks.sort(Comparator.comparing(extractNumber, sortIntegersNaturallyWithNullsLast));

NB: Another option is that you make tasks have a natural order:

public class Task implements Comparable<Task> {
    private Integer number;
    .. rest of class here ..

    @Override public int compareTo(Task other) {
        Integer o = other.number;
        if (number == null && o == null) return 0;
        if (number != null) return -1;
        if (o != null) return +1;
        return number.compareTo(o);
    }
}

and then you can just go with Comparator.naturalOrder(): listOfTasks.sort(Comparator.naturalOrder()).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=19120&siteId=1