Reputation: 5680
I want to have a null-safe comparator, but it does not work:
Comparator<Item> sort_high = (i1, i2)-> Double.compare(i2.getUser().getValue(), i1.getUser().getValue());
items.sort(Comparator.nullsFirst(sort_high));
However, I get a NPE, if item.getUser().getValue() (or item.getUser()) is null.
at Item.lambda$1(Item.java:270)
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.List.sort(List.java:478)
What is wrong?
Upvotes: 8
Views: 12218
Reputation: 99
Another possibility, that is slightly more readable but does not distinguish between an item being null and an item having a user that is null, is the following:
Comparator<Item> sortLow = Comparator.comparing(item -> Optional.ofNullable(item)
.map(Item::getUser)
.map(User::getValue)
.orElse(null),
Comparator.nullsFirst(Comparator.naturalOrder())
);
Upvotes: 0
Reputation: 131346
Comparator.nullsFirst()
doesn't mean that any NullPointerException
thrown during the compare()
method will be caught.
It says rather that a null
compared object with always be less than a non null
compared object and that if both are null
, they are considered equal.
<T> Comparator<T> java.util.Comparator.nullsFirst(Comparator<? super
T> comparator)
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.
In your case :
Comparator<Item> sort_high = (i1, i2)-> Double.compare(i2.getUser().getValue(), i1.getUser().getValue());
Compared objects are not null
, that is i1
or i2
but fields of them are: i1.getUser()
or i2.getUser()
.
You don't have a good use case for using directly Comparator.nullsFirst()
.
As workaround, you could use it to compare the User
field in your compare()
implementation as soon as one of two compared objects present a null User
field.
I think that it is a good trade off between handling yourself null
case and readability of the code :
private Comparator<User> comparatorUserNullFirst = Comparator.nullsFirst(Comparator.comparingDouble(User::getValue));
Comparator<Item> sortHigh = (i1, i2) -> {
if (i1.getUser() == null || i2.getUser() == null) {
return comparatorUserNullFirst.compare(i1.getUser(), i2.getUser());
}
return Double.compare(i2.getUser().getValue(), i1.getUser().getValue());
};
Upvotes: 4
Reputation: 46323
nullsFirst
will take care of null Item
s ("i1
"), but once two valid objects are found, your Comparator
is invoked and you need to handle internal null
references.
In your case, you could use something like:
items.sort(
Comparator.nullsFirst(
Comparator.comparing(Item::getUser,
Comparator.nullsFirst(Comparator.comparingDouble(User::getValue))
)
)
);
(Assuming getValue()
returns a double
)
But I hardly recommend such convoluted code.
Upvotes: 10