user51
user51

Reputation: 10163

NullPointerException when using Comparator.nullsLast

I have below code with 2 classes MyRange and MyCustomValue -

class MyRange {
    private Long id;
    private Double minValue;
    private Double maxValue;

    // getters and setters
    // equals, hashCode and toString

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        MyRange other = (MyRange) obj;
        return Objects.equals(this.id, other.id) && 
               Objects.equals(this.minValue, other.minValue) &&
               Objects.equals(this.maxValue, other.maxValue);
    }
}

class MyCustomValue {
    private String value;
    private MyRange myrange;

    //getters and setters
    // equals, hashCode and toString

}

If the value is null in MyCustomValue I want it in last. So I write the comparator like below

public static final Comparator<MyCustomValue> externalMVComparator = (emv1, emv2) -> {
    if(emv1.getValue() != null && emv2.getValue() == null) {
        return -1;
    } else if (emv1.getValue() == null && emv2.getValue() != null) {
        return 1;
    } else {
        return myrangeMinValueComparator.compare(emv1, emv2);
    }
}

private static final Comparator<MyRange> minValueComparator =  Comparator.nullsLast(Comparator.comparingDouble(value -> value.getMinValue()));
private static final Comparator<MyCustomValue> myrangeMinValueComparator = Comparator.nullsLast(Comparator.comparing(MyCustomValue::getMyrange, minValueComparator));

The above comparator is working fine. So I decided to change externalMVComparator like below (i.e, using thenComparing for more readability)

private static final Comparator<MyCustomValue> valueComparator = Comparator.nullsLast(Comparator.comparing(MyCustomValue::getValue));
public static final Comparator<MyCustomValue> externalMVComparator2 = Comparator.nullsLast(valueComparator.thenComparing(myrangeMinValueComparator));

But sorting the list with externalMVComparator2 results in NullPointerException. What is that wrong I'm doie mistake in my code?

Code used for testing -

MyCustomValue emv1 = new MyCustomValue("v1", new MyRange(1L, 0.71, 0.79));
MyCustomValue emv2 = new MyCustomValue(null, new MyRange(2L, 0.53, 0.65));
MyCustomValue emv3 = new MyCustomValue("v2", new MyRange(3L, 0.28, 0.42));
MyCustomValue emv4 = new MyCustomValue(null, new MyRange(4L, 0.06, 0.27));
List<MyCustomValue> shuffledList1 = Arrays.asList(emv1, emv2, emv3, emv4);
Collections.shuffle(shuffledList1);
shuffledList1.sort(MyCustomValue.externalMVComparator2);
Assert.assertEquals(shuffledList1, Arrays.asList(emv3, emv1, emv4, emv2));

error stacktrace -

    Exception in thread "main" java.lang.NullPointerException
    at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
    at java.util.Comparator.lambda$thenComparing$36697e65$1(Comparator.java:216)
    at java.util.Comparators$NullComparator.compare(Comparators.java:83)
    at java.util.Comparators$NullComparator.compare(Comparators.java:83)
    at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
    at java.util.TimSort.sort(TimSort.java:220)
    at java.util.Arrays.sort(Arrays.java:1438)
    at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
    at TestNullComparator.main(TestNullComparator.java:15)

Upvotes: 5

Views: 5206

Answers (1)

Misha
Misha

Reputation: 28133

The problem is in the following line (I removed Comparator. for clarity):

Comparator<MyCustomValue> valueComparator = nullsLast(comparing(MyCustomValue::getValue));

The Comparator you made will handle null values of MyCustomValue type. It will NOT handle nulls returned by getValue. You have to use the 2-argument version of Comparator.comparing and supply a null-safe comparator for the values:

valueComparator = comparing(MyCustomValue::getValue, nullsLast(naturalOrder()));

The above will handle the common case where you actually want to sort by value. As I look at your code, I think you mean to only use the value for null check and otherwise don't want to sort by it. If that is the case, you can use nullsLast( (x,y) -> 0) as the null-safe 2nd argument for comparing that will consider all strings as equal. You can also use valueComparator = comparing(mcv -> mcv.getValue() == null) because true goes after false in natural ordering, but that might be less clear.

If you also want to handle nulls of MyCustomValue, you would have to wrap it in nullsLast again.

Upvotes: 10

Related Questions