this may be a simple question but I would like to understand it clearly...
I have a code like this:
public final class Persona
{
private final int id;
private final String name
public Persona(final int id,final String name)
{
this.id = id;
this.name = name;
}
public int getId(){return id;}
public String getName(){return name;}
@Override
public String toString(){return "Persona{" + "id=" + id + ", name=" + name+'}';}
}
And I am testing this code:
import static java.util.Comparator.*;
private void nullsFirstTesting()
{
final Comparator<Persona>comparator = comparing(Persona::getName,nullsFirst(naturalOrder()));
final List<Persona>persons = Arrays.asList(new Persona(1,"Cristian"),new Persona(2,"Guadalupe"),new Persona(3,"Cristina"),new Persona(4,"Chinga"),new Persona(5,null));
persons
.stream()
.sorted(comparator)
.forEach(System.out::println);
}
This shows the following results:
Persona{id=5, name=null}
Persona{id=4, name=Chinga}
Persona{id=1, name=Cristian}
Persona{id=3, name=Cristina}
Persona{id=2, name=Guadalupe}
These results are OK with me but I have a problem understanding.
When I ignore the new Persona(5,null)
object and I pass the comparator:
final Comparator<Persona>comparator = comparing(Persona::getName);
It works like a charm. My sorting is by natural order of name property
. The problem arises when I add the object with name=null
, I just thought I would need my comparator like this.
final Comparator<Persona>comparator = comparing(Persona::getName,nullsFirst());
My thought was erroneous: "OK, when name is non-null, they are sorted in natural order of name
, just like the previous comparator, and if they are null
they will be first but my non-null names will still be sorted in natural order".
But the right code is this:
final Comparator<Persona>comparator = comparing(Persona::getName,nullsFirst(naturalOrder()));
I don't understand the parameter to nullsFirst
. I just thought the natural order of name
would explicitly [default] even handle null
values.
But the docs say:
Returns a null-friendly comparator that considers
null
to be less than non-null. When both arenull
, they are considered equal. If both are non-null, the specifiedComparator
is used to determine the order. If the specified comparator isnull
, then the returned comparator considers all non-null values to be equal.
This line: "If both are non-null, the specified Comparator
is used to determine the order."
I am confused when and how the natural order should be explicitly set or when they are inferred.
The "natural order" comparator, which is what you get when you use comparing
with only one parameter, does not handle nulls. (I'm not sure where you got the idea that it did.) The "natural order" of a Comparable
class is defined by the compareTo()
method, which is used like this:
obj1.compareTo(obj2)
Obviously this won't work if obj1
is null; for String
, it will also throw an exception if obj2
is null.
The naturalOrder()
method returns a Comparator
that compares two objects. The javadoc explicitly says that this comparator throws NullPointerException
when comparing null.
The nullsFirst()
method (and nullsLast()
similarly) basically transforms a Comparator
to a new Comparator
. You put in a comparator that may throw an exception if it tries to compare null, and it spits out a new comparator that works the same way except that it allows null arguments. So that's why you need a parameter to nullsFirst
--because it builds a new comparator on top of an existing comparator, and you tell it what the existing comparator is.
So why doesn't it give you the natural order if you leave out the parameter? Because they didn't define it that way. nullsFirst
is defined in the javadoc to take a parameter:
static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)
I think that if the designers wanted to, they could have added an overload that takes no parameters:
static <T> Comparator<T> nullsFirst() // note: not legal
that would be the same as using nullsFirst(naturalOrder())
. But they didn't, so you can't use it like that.