java 8 中的 Collections.sort 在比较器返回 0 时不作为 java 6
Collections.sort in java 8 is not working as java 6 while comparator returning 0
最近我将我们的应用程序 jdk 从 Java 6 更新为 Java 8,但源语言级别仍然保持为 Java 6。更改我们的单元测试之一后失败了。
我注意到 LinkedList 的 Collections.sort 在 Java 8 和 Java 6 中工作不同。即使我在源级别 java 8 和 JDk 1.8 我得到相同的不同行为。
重现问题:
定义以下枚举:
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 + ')';
}
}
和一个主要 Class 如下:
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);
}
}
运行 Java 6 下的代码的输出是:
"C:\Program Files\Java\jdk1.6.0_45\bin\java"
[B(0), C(0), D(0), A(1), E(2)]
运行 代码在 java 8 下的输出是:
"C:\Program Files (x86)\Java\jdk1.8.0_161\bin\java"
[A(1), B(0), C(0), D(0), E(2)]
我将类型从 LinkedList
更改为 ArrayList
并且我得到了相同的结果但是如果我如下更改比较器然后 Java 8 将对数组进行排序:
Collections.sort(weightList, new Comparator<Weight>() {
@Override
public int compare(Weight o1, Weight o2) {
return o1.getWeight() > o2.getWeight()? 1:-1;
}
});
如您所见,java 8 似乎没有正确排序代码。 Java 中有错误还是我像往常一样遗漏了什么?
public int compare(Weight o1, Weight o2) {
return o1.getWeight() > o2.getWeight()? 1:0;
}
此定义不符合 Java API 预期的合同。如果您向 sort
传递无效的 Comparator
.
,则其行为未定义
The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x))
for all x
and y
.
The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0))
implies compare(x, z)>0
.
Finally, the implementor must ensure that compare(x, y)==0
implies that sgn(compare(x, z))==sgn(compare(y, z))
for all z
.
如果 o1 < o2
,您的函数不会 return -1
。它 returns 0
,错误地断言参数相等。这也会导致它在第一个项目符号中失败:如果翻转参数,结果需要改变符号。
正确的实施方式是:
public int compare(Weight o1, Weight o2) {
return Integer.compare(o1.getWeight(), o2.getWeight());
}
正如 Boris 在其评论中建议的那样:您的 compare()
方法不正确。
看看你的实现:
return o1.getWeight() > o2.getWeight()? 1:0;
假设您的列表中有三个对象:
- o1 : 权重 3
- o2 : 权重 5
- o3 : 权重 5
现在假设 sort()
以这种方式为您的列表调用 compare()
:
compare(o1, o2)
returns 0
compare(o2, o3)
returns 0
通过传递性,意味着o1
、o2
和o3
具有相同的顺序。你不希望我假设。
如果它在 Java 6 上有效,那么它在实现中只是 "chance" 而不是您应该可靠地期望的结果。
要解决您的问题,您必须处理 3 种情况(优、劣或等):
@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;
}
最后相当于写:return Integer.compare(o1.getWeight(), o2.getWeight())
.
请注意,Java 8 中的 less error prone 是使用 Comparator.comparingInt()
工厂方法并直接使用 sort()
在 List
中引入的方法:
weightList.sort(Comparator.comparingInt(Weight::getWeight));
内部排序算法 was changed to Tim Sort for objects and a dual-pivot quicksort for primitives 从 JDK 7.
因为你的比较器是错误的(它为 non-equal 值返回 0),你很幸运它之前没有坏掉。现在它按预期中断了。
最近我将我们的应用程序 jdk 从 Java 6 更新为 Java 8,但源语言级别仍然保持为 Java 6。更改我们的单元测试之一后失败了。 我注意到 LinkedList 的 Collections.sort 在 Java 8 和 Java 6 中工作不同。即使我在源级别 java 8 和 JDk 1.8 我得到相同的不同行为。 重现问题: 定义以下枚举:
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 + ')';
}
}
和一个主要 Class 如下:
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);
}
}
运行 Java 6 下的代码的输出是: "C:\Program Files\Java\jdk1.6.0_45\bin\java"
[B(0), C(0), D(0), A(1), E(2)]
运行 代码在 java 8 下的输出是:
"C:\Program Files (x86)\Java\jdk1.8.0_161\bin\java"
[A(1), B(0), C(0), D(0), E(2)]
我将类型从 LinkedList
更改为 ArrayList
并且我得到了相同的结果但是如果我如下更改比较器然后 Java 8 将对数组进行排序:
Collections.sort(weightList, new Comparator<Weight>() {
@Override
public int compare(Weight o1, Weight o2) {
return o1.getWeight() > o2.getWeight()? 1:-1;
}
});
如您所见,java 8 似乎没有正确排序代码。 Java 中有错误还是我像往常一样遗漏了什么?
public int compare(Weight o1, Weight o2) {
return o1.getWeight() > o2.getWeight()? 1:0;
}
此定义不符合 Java API 预期的合同。如果您向 sort
传递无效的 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
.
如果 o1 < o2
,您的函数不会 return -1
。它 returns 0
,错误地断言参数相等。这也会导致它在第一个项目符号中失败:如果翻转参数,结果需要改变符号。
正确的实施方式是:
public int compare(Weight o1, Weight o2) {
return Integer.compare(o1.getWeight(), o2.getWeight());
}
正如 Boris 在其评论中建议的那样:您的 compare()
方法不正确。
看看你的实现:
return o1.getWeight() > o2.getWeight()? 1:0;
假设您的列表中有三个对象:
- o1 : 权重 3
- o2 : 权重 5
- o3 : 权重 5
现在假设 sort()
以这种方式为您的列表调用 compare()
:
compare(o1, o2)
returns 0
compare(o2, o3)
returns 0
通过传递性,意味着o1
、o2
和o3
具有相同的顺序。你不希望我假设。
如果它在 Java 6 上有效,那么它在实现中只是 "chance" 而不是您应该可靠地期望的结果。
要解决您的问题,您必须处理 3 种情况(优、劣或等):
@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;
}
最后相当于写:return Integer.compare(o1.getWeight(), o2.getWeight())
.
请注意,Java 8 中的 less error prone 是使用 Comparator.comparingInt()
工厂方法并直接使用 sort()
在 List
中引入的方法:
weightList.sort(Comparator.comparingInt(Weight::getWeight));
内部排序算法 was changed to Tim Sort for objects and a dual-pivot quicksort for primitives 从 JDK 7.
因为你的比较器是错误的(它为 non-equal 值返回 0),你很幸运它之前没有坏掉。现在它按预期中断了。