Reputation: 2129
Recently I updated our application jdk from Java 6 to Java 8 but kept source language level still as Java 6. After change one of our unit tests was failing. I noticed that Collections.sort for LinkedList is working different in Java 8 and Java 6. Even when I am Source Level java 8 with JDk 1.8 I am getting same different behavior. To recreate problem: Defining the enum below:
public enum Weight {
A(1), B(0), C(0), D(0), E(2);
public int getWeight() {
return weight;
}
private int weight;
Weight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return name() + '(' + weight + ')';
}
}
and a main Class as below:
public class Main {
public static void main(String[] args) {
List<Weight> weightList = new LinkedList<Weight>();
weightList.add(Weight.A);
weightList.add(Weight.B);
weightList.add(Weight.C);
weightList.add(Weight.D);
weightList.add(Weight.E);
Collections.sort(weightList, new Comparator<Weight>() {
@Override
public int compare(Weight o1, Weight o2) {
return o1.getWeight() > o2.getWeight()? 1:0;
}
});
System.out.print(weightList);
}
}
The output for running the code under Java 6 is: "C:\Program Files\Java\jdk1.6.0_45\bin\java"
[B(0), C(0), D(0), A(1), E(2)]
and output for running code under java 8 is:
"C:\Program Files (x86)\Java\jdk1.8.0_161\bin\java"
[A(1), B(0), C(0), D(0), E(2)]
I changed the type from LinkedList
to ArrayList
and I am getting the same result but if I am changing the comparator as below then Java 8 is going to sort the array:
Collections.sort(weightList, new Comparator<Weight>() {
@Override
public int compare(Weight o1, Weight o2) {
return o1.getWeight() > o2.getWeight()? 1:-1;
}
});
As you see it seems that the java 8 is not sorting the code properly. Is there a bug in Java or am I missing something as usual?
Upvotes: 5
Views: 5824
Reputation: 361585
public int compare(Weight o1, Weight o2) {
return o1.getWeight() > o2.getWeight()? 1:0;
}
This definition doesn't meet the contract expected by the Java API. sort
's behavior is undefined if you pass it an invalid Comparator
.
The implementor must ensure that
sgn(compare(x, y)) == -sgn(compare(y, x))
for allx
andy
.The implementor must also ensure that the relation is transitive:
((compare(x, y)>0) && (compare(y, z)>0))
impliescompare(x, z)>0
.Finally, the implementor must ensure that
compare(x, y)==0
implies thatsgn(compare(x, z))==sgn(compare(y, z))
for allz
.
Your function doesn't return -1
if o1 < o2
. It returns 0
, incorrectly asserting that the arguments are equal. This also causes it to fail the first bullet: if you flip the arguments, the result needs to change sign.
A correct implementation would be:
public int compare(Weight o1, Weight o2) {
return Integer.compare(o1.getWeight(), o2.getWeight());
}
Upvotes: 11
Reputation: 131316
As Boris suggested in its comment : your compare()
method is not correct.
Look at your implementation :
return o1.getWeight() > o2.getWeight()? 1:0;
Suppose you have three objects in your list:
Now suppose that sort()
invokes compare()
in this way for your list :
compare(o1, o2)
returns 0
compare(o2, o3)
returns 0
By transitivity, it means that o1
, o2
and o3
have the same order. You don't want that I assume.
If it works on Java 6, it's simply "chance" in the implementation and not a result that you should reliably expect to.
To solve your issue, you have to handle the 3 cases (superior, inferior or equal) :
@Override
public int compare(Weight o1, Weight o2) {
if (o1.getWeight() > o2.getWeight()){
return 1;
}
else if (o1.getWeight() < o2.getWeight()){
return -1;
}
return 0;
}
Which is equivalent finally to write : return Integer.compare(o1.getWeight(), o2.getWeight())
.
Note that a much better alternative and less error prone in Java 8 is using the Comparator.comparingInt()
factory method and used directly the sort()
method that was introduced in List
:
weightList.sort(Comparator.comparingInt(Weight::getWeight));
Upvotes: 5
Reputation: 34460
The internal sort algorithm was changed to Tim Sort for objects and a dual-pivot quicksort for primitives as from JDK 7.
As your comparator was wrong (it was returning 0 for non-equal values), you were very lucky that it didn't break before. Now it breaks as expected.
Upvotes: 15