javacomparatorcomparable

Java8 Null-safe comparison


Having issues with comparing two products. I wish to compare the vintage (which is optional) attribute of each of them. But whenever this attribute is null, a NPE is thrown. I thought with Comparator.nullsLast(..) I can deal with null values... But it seems I either have a misunderstanding of how this works or there's something wrong with the code. What do I need to change to get this work null-friendly?

@Override
public int compare(IProduct product1, IProduct product2) throws ProductComparisonException {

    Comparator<IShopProduct> comparator =
        Comparator.nullsLast(Comparator.comparing(IShopProduct::getVintage));

    return comparator.compare((IShopProduct)product1.getProvidedProductData(),
                              (IShopProduct)product2.getProvidedProductData());
}

Thanks in advance


Solution

  • It should be

    Comparator<IShopProduct> comparator = 
                Comparator.comparing( IShopProduct::getVintage, 
                                 Comparator.nullsLast(Comparator.naturalOrder()));
    

    Comparator.nullsFirst()/nullsLast() consider null value being greater/smaller than nonNull object

    Edit

    This is implementation of Comparator.comparing():

    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }
    

    As you can see it calls keyExtractor.apply(c1).compareTo() so it will throw NPE if keyExtractor.apply(c1) is null

    My suggested code using the following function:

    public static <T, U> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(keyComparator);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                              keyExtractor.apply(c2));
    }
    

    Basically it will extracts the value, then pass the comparing values to Comparator.

    The values will be passed to the naturalOrder() comparator which resolved to value1.compareTo(value2). Normally it will throw NPE but we have wrapped it with Comparator.nullsLast which have special null handler.