In Java8 I have an algorithm I cannot really change that orders a particular structure as follows:
- All elements in category XXX must go to the end of the array (does not matter the order)
- All other elements can have any order
It was implemented like this:
list.sort((o1, o2) -> {
boolean isXXX1 = "XXX".equals(o1.getCategory());
boolean isXXX2 = "XXX".equals(o2.getCategory());
if(isXXX1) {
return isXXX2 ? 0 : 1;
} else if(isXXX2) {
return -1;
}
return o1.hashCode() - o2.hashCode();
});
Hashcode is created by ReflectionashCode on the PoJo objects, so I do not expect weird collisions.
Occasionally, apparently at random, this piece of code throws IllegalArgumentException: Comparison method violates its general contract!
I tried to debug it, but even with the same data in debug mode it does not break. What could be the cause? I dont see any case where the sorting does not respect the contract
TL;DR
Never use minus as a comparator function.
Your comparator ends with
return o1.hashCode() - o2.hashCode();Unfortunately, developers come up with this idea all the time and for some tests (i.e. with small values), it might seem to work.
The problem is that comparing arbitrary integer values (and hash codes may use the entire
intvalue range) can lead to overflow, as the distance between twointvalues may be larger than theintvalue range.The correct idiom would be
return Integer.compare(o1.hashCode(), o2.hashCode());There is no problem if a hash collision occurs, as that simply means that the two elements would be treated as equal for this particular sort operation, which is fine if you don’t care about their relative order anyway. In fact, you can treat all elements whose relative order is irrelevant as equal, replacing the last line with
return 0;.Or, simplify the entire operation with
Since
Booleanvalues are comparable and itscompareTomethod will treattrueas larger thanfalse, it will move the matching elements to the end of the list.