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

通过传递性,意味着o1o2o3具有相同的顺序。你不希望我假设。
如果它在 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),你很幸运它之前没有坏掉。现在它按预期中断了。