当比较器返回0时,java 8中的Collections.sort不能用作java 6

问题描述 投票:5回答:3

最近我将我们的应用程序jdk从Java 6更新为Java 8,但仍将源语言级别保持为Java 6.更改后,我们的一个单元测试失败了。我注意到LinkedList的Collections.sort在Java 8和Java 6中的工作方式不同。即使我是JDk 1.8的Source Level java 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 + ')';
    }
}

和一个主类如下:

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中是否有错误或者我像往常一样遗漏了什么?

java sorting collections java-8 comparator
3个回答
14
投票

来自JDK 7的内部排序算法was changed to Tim Sort for objects and a dual-pivot quicksort for primitives

由于你的比较器是错误的(它为非等值返回0),你很幸运它没有破坏。现在它按预期打破了。


11
投票
public int compare(Weight o1, Weight o2) {
    return o1.getWeight() > o2.getWeight()? 1:0;
}

该定义不符合Java API预期的合同。如果你传递一个无效的sortComparator的行为是不确定的。

  • 实施者必须确保所有sgn(compare(x, y)) == -sgn(compare(y, x))xy
  • 实现者还必须确保关系是可传递的:((compare(x, y)>0) && (compare(y, z)>0))暗示compare(x, z)>0
  • 最后,执行者必须确保compare(x, y)==0暗示所有sgn(compare(x, z))==sgn(compare(y, z))z

如果-1,你的函数不会返回o1 < o2。它返回0,错误地断言参数是相等的。这也会导致第一个子弹失败:如果你翻转参数,结果需要改变符号。

正确的实施将是:

public int compare(Weight o1, Weight o2) {
    return Integer.compare(o1.getWeight(), o2.getWeight());
}

5
投票

正如鲍里斯在评论中所说:你的compare()方法不正确。

看看你的实现:

 return o1.getWeight() > o2.getWeight()? 1:0;

假设您的列表中有三个对象:

  • o1:重量3
  • o2:重量5
  • o3:重量5

现在假设sort()以这种方式为你的列表调用compare()

compare(o1, o2)返回0

compare(o2, o3)返回0

通过传递性,这意味着o1o2o3具有相同的顺序。你不想要我假设。 如果它适用于Java 6,它只是实现中的“机会”而不是你应该可靠的结果。

要解决您的问题,您必须处理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));
© www.soinside.com 2019 - 2024. All rights reserved.