I am trying to implement the comparable interface in Java for an object that I need to sort by two different columns/variables. I tried multiple approaches, and this one is the best so far:
public int compareTo(Object o) {
Match m = (Match)o;
int diff = m.matches - matches;
if (diff == 0) {
if (distance > m.distance) {
return 1;
} else if (distance < m.distance) {
return -1;
} else {
return 0;
}
} else {
return diff;
}
}
but it still fails with
java.lang.IllegalArgumentException: Comparison method violates its general contract!
Any ideas what I'm doing wrong?
Side note 1: NPEs/ClassCastExceptions are expected, if o is null or of an unsuitable class - that is not the issue here.
Side note 2: I know about the change in the sorting algorithm in JDK 1.7, but I don't really see where I'm violating the contract here. So turning off the exception seems like the wrong solution.
Since you say distance
is a double, you likely have the same problem as described here:
Java error: "Comparison method violates its general contract!"
Perhaps:
public int compareTo(Object o) {
Match m = (Match)o;
int diff = m.matches - matches;
if (diff == 0) {
return Double.compare(distance, m.distance);
} else {
return diff;
}
}
However ideally you should use the built-in comparison methods as I state below. The above code is an example of the "minimum change needed", illustrating the key problem.
Also, as @fabian-barney states in his answer, you should avoid taking the direct difference, and instead utilise the built-in comparison methods. So you should have something like:
public int compareTo(Object o) {
Match m = (Match) o;
return m.matches == matches ? Double.compare(m.distance, distance) : Integer.compare(m.matches, matches);
}
This way, Double.compare
will handle the NaN values for you. For any number x
(other than NaN) Double.compare(x, Double.NaN) == -1
will return true (i.e., NaN is considered greater than any other number).
Note here that you are OK using ==
with int
s but it is more complicated with double
because Double.NaN != Double.NaN
. However, new Double(Double.NaN).equals(Double.NaN)
is true. See Why is Java's Double.compare(double, double) implemented the way it is? for a nice discussion.
To see an example of why your original implementation might break the contract if you have NaNs, see Java compareTo documentation. There we have:
Finally, the implementer must ensure that x.compareTo(y)==0 implies that sgn(x.compareTo(z)) == sgn(y.compareTo(z)), for all z.
So imagine you have x = NaN
and y = 5
and z = 6
, then:
x.compareTo(y) == 0
(since NaN > 5
and NaN < 5
are false)x.compareTo(z) == 0
(same reasoning)y.compareTo(z) == -1
(y < z).So 2 and 3 (+sgn
) are not equal as required.