Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Comparator nullsFirst naturalOrder confused

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 are null, they are considered equal. If both are non-null, the specified Comparator is used to determine the order. If the specified comparator is null, 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.

like image 968
chiperortiz Avatar asked Oct 14 '14 00:10

chiperortiz


People also ask

What does comparator naturalOrder do?

The naturalOrder() method of Comparator Interface in Java returns a comparator that use to compare Comparable objects in natural order. The returned comparator by this method is serializable and throws NullPointerException when comparing null.

How do you handle null in comparator?

When both elements are null, then they are considered equal. When both elements are non-null, the specified Comparator determines the order. If specified comparator is null, then the returned comparator considers all non-null elements equal.

What does comparator comparing do in Java?

comparator,” Java Comparator compares two Java objects in a “compare(Object 01, Object 02)” format. Using configurable methods, Java Comparator can compare objects to return an integer based on a positive, equal or negative comparison.

What does a comparator compare?

A comparator circuit compares two voltages and outputs either a 1 (the voltage at the plus side) or a 0 (the voltage at the negative side) to indicate which is larger. Comparators are often used, for example, to check whether an input has reached some predetermined value.


2 Answers

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.

like image 77
ajb Avatar answered Oct 01 '22 17:10

ajb


Try:

final Comparator<Persona> comparator =
  comparing(Persona::getName, nullsFirst(naturalOrder()));
like image 22
zgmnkv Avatar answered Oct 01 '22 18:10

zgmnkv