compare
function that compares two objects. An alternative to the comparator is the natural order of the objects: the object implements the Comparable
interface, which contains a compareTo
method that allows you to compare this object with another. The comparison function should return 0 if the objects are equal, a negative number (usually -1) if the first object is less than the second, and a positive number (usually 1) if the first is larger. Typically, the implementation of such a function is not difficult, but there is one case that many people forget.TreeMap
. These algorithms are tied to three important properties of the comparison function: reflexivity (comparing an element with itself always gives 0), antisymmetry (comparing A with B and B with A should give a different sign) and transitivity (if comparing A with B and B with C gives the same sign, then the comparison of A with C should give the same). If the comparing function does not satisfy these properties, the algorithm may produce a completely unpredictable result. And most likely you will not get any exception, just the result will be incorrect. public class DoubleHolder implements Comparable<DoubleHolder> { double d; public DoubleHolder(double d) { this.d = d; } @Override public int compareTo(DoubleHolder o) { return d > od ? 1 : d == od ? 0 : -1; } @Override public String toString() { return String.valueOf(d); } }
new DoubleHolder(-0.0).compareTo(new DoubleHolder(+0.0))
returns 0. Sometimes it is not a bad thing, but in the case of sorting, elements with +0.0 and -0.0 will be located in an arbitrary order that will look ugly. However, all this is minor compared to NaN. The NaN number (in both double and float types) is a rather specific thing. It is not more, not less and not equal to any other number. As a result, we immediately get a violation of the properties of the comparison: DoubleHolder nan = new DoubleHolder(Double.NaN); DoubleHolder zero = new DoubleHolder(0.0); System.out.println("nan.compareTo(nan): "+nan.compareTo(nan)); System.out.println("nan.compareTo(zero): "+nan.compareTo(zero)); System.out.println("zero.compareTo(nan): "+zero.compareTo(nan));
nan.compareTo(nan): -1 nan.compareTo(zero): -1 zero.compareTo(nan): -1
public int compareTo(DoubleHolder o) { return d > od ? 1 : d < od ? -1 : 0; }
DoubleHolder nan = new DoubleHolder(Double.NaN); DoubleHolder zero = new DoubleHolder(0.0); DoubleHolder one = new DoubleHolder(1.0); System.out.println("zero.compareTo(nan): "+zero.compareTo(nan)); System.out.println("nan.compareTo(one): "+nan.compareTo(one)); System.out.println("zero.compareTo(one): "+zero.compareTo(one));
zero.compareTo(nan): 0 nan.compareTo(one): 0 zero.compareTo(one): -1
List<DoubleHolder> data = new ArrayList<>(); for(int i=1; i<=10; i++) { data.add(new DoubleHolder(i)); } data.add(new DoubleHolder(Double.NaN)); for(int i=0; i<10; i++) { Collections.shuffle(data); Collections.sort(data); System.out.println(data); }
[NaN, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [NaN, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [1.0, NaN, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [NaN, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, NaN] [2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, NaN, 1.0, 9.0, 10.0] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 10.0, NaN, 9.0] [NaN, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [NaN, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [1.0, NaN, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]
compareTo
: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 8.0, 9.0, NaN, 7.0, 10.0] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, NaN] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, NaN] [1.0, 2.0, 9.0, 10.0, NaN, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, NaN] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, NaN] [2.0, 6.0, NaN, 1.0, 3.0, 4.0, 5.0, 7.0, 8.0, 9.0, 10.0] [2.0, 4.0, NaN, 1.0, 3.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [1.0, 3.0, NaN, 2.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, NaN]
DoubleHolder
as a key, nothing good will happen either. Take, for example, the TreeSet
. With the second implementation of compareTo
everything is quite simple: since an element containing NaN is equal to any other, it will not be possible to insert it into a non-empty set, because it decides that such an element already exists. But beware if you insert the NaN element first: after that, it will not be possible to add anything else to the set. Set<DoubleHolder> set = new TreeSet<>(); for(int i=0; i<50; i++) { set.add(new DoubleHolder( i%10==0 ? Double.NaN : i%10 )); } System.out.println(set);
[NaN, NaN, 1.0, 2.0, 3.0, NaN, NaN, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, NaN]
public int compareTo(DoubleHolder o) { return Double.compare(d, od); }
public class MyObject implements Comparable<MyObject> { double d; int i; String s; char c; @Override public int compareTo(MyObject o) { int result; result = Double.compare(d, od); if(result != 0) return result; result = Integer.compare(i, oi); if(result != 0) return result; result = s.compareTo(os); if(result != 0) return result; result = Character.compare(c, oc); return result; } }
@Override public int compareTo(MyObject o) { return ComparisonChain.start() .compare(d, od) .compare(i, oi) .compare(s, os) .compare(c, oc) .result(); }
Source: https://habr.com/ru/post/247015/
All Articles