Eduardo H.
Eduardo H.

Reputation: 45

Exception 'Comparison Method Violates Its General Contract!' when comparing two date properties of an object

First of all, I've already looked at the many questions and answers about this exception but most of them are just about comparing simple integers or the same properties. What I have is an object that has two dates, that when one is null I will use the other one to compare, ex of the class:

public class MyClass {
  private LocalDate primaryDate;
  private LocalDate secondaryDate;
  private String Code;
}

the comparison method is:

 private List<MyClass> sortByDates(final List<MyClass> listOfClass) {
    Comparator<MyClass> comparator = ((Comparator<MyClass>) (first, second) -> {
        if (first.getPrimaryDate() == null || second.getPrimaryDate() == null) {
            return first.getSecondaryDate().compareTo(second.getSecondaryDate());
        }
        return first.getPrimaryDate().compareTo(second.getPrimaryDate());
    }).reversed().thenComparing(MyClass::getCodeAsLong);

    return listOfClass.stream()
        .sorted(comparator)
        .collect(Collectors.toList());
}

Obs1: secondaryDate is never null but I have to use the primaryDate as first option to compare, and primaryDate is never less than secondaryDate
Obs2: Code property is saved as a string but don't have any letter, so it can be converted to long

Upvotes: 1

Views: 1962

Answers (1)

samabcde
samabcde

Reputation: 8124

The only problem is how your handle null for primaryDate.
Check the following example:

MyClass primaryDate secondaryDate
A null 1/1/2021
B 1/1/2023 1/1/2022
C 1/1/2024 1/1/2020

By your comparator, B > A and A > C which imply B > C
However when you compare B with C. B < C, this violate the "General Contract".

In general, when comparing nullable field, we can make use of nullsFirst or nullsLast:

Comparator<MyClass> comparator = Comparator.nullsFirst(Comparator
                                           .comparing(MyClass::getPrimaryDate))
                                           .thenComparing(MyClass::getSecondaryDate)
                                           .reversed()
                                           .thenComparing(MyClass::getCodeAsLong);

Upvotes: 4

Related Questions