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

Gov*_*van 5 java sorting collections comparator java-8

最近我将我们的应用程序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 + ')';
    }
}
Run Code Online (Sandbox Code Playgroud)

和一个主类如下:

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);

    }
}
Run Code Online (Sandbox Code Playgroud)

在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)]

我从变型LinkedListArrayList和我得到同样的结果,但如果我改变比较如下那么Java 8将会对数组进行排序:

Collections.sort(weightList, new Comparator<Weight>() {
            @Override
            public int compare(Weight o1, Weight o2) {
                return o1.getWeight() > o2.getWeight()? 1:-1;
            }
        });
Run Code Online (Sandbox Code Playgroud)

如您所见,java 8似乎没有正确排序代码.Java中是否有错误或者我像往常一样遗漏了什么?

Fed*_*ner 15

从JDK 7开始,内部排序算法更改为对象的Tim Sort和基元的双轴快速排序.

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


Joh*_*ica 11

public int compare(Weight o1, Weight o2) {
    return o1.getWeight() > o2.getWeight()? 1:0;
}
Run Code Online (Sandbox Code Playgroud)

此定义不符合Java API预期的合同.sort如果你传递一个无效的行为是不明确的Comparator.

  • 实施者必须确保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());
}
Run Code Online (Sandbox Code Playgroud)

  • 或者只是`Comparator.comparing(Weight :: getWeight)`. (5认同)
  • 在编译期间选择的源级别仅影响可在源文件中使用的语言构造集.它不会影响JDK函数的行为. (4认同)
  • @lexicore或更好的`Comparator.comparingInt(Weight :: getWeight);` (3认同)
  • 我想这个问题也是为什么6和8显示不同的结果. (2认同)

dav*_*xxx 5

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

看看你的实现:

 return o1.getWeight() > o2.getWeight()? 1:0;
Run Code Online (Sandbox Code Playgroud)

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

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

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

compare(o1, o2) 回报 0

compare(o2, o3) 回报 0

通过传递性,它意味着o1,o2o3具有相同的顺序.你不想要我假设.
如果它适用于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;
}
Run Code Online (Sandbox Code Playgroud)

这最终等同于写:return Integer.compare(o1.getWeight(), o2.getWeight()).

请注意,Java 8中更好的替代方案和更少的错误是使用Comparator.comparingInt()工厂方法并直接使用在以下sort()方法中引入的方法List:

weightList.sort(Comparator.comparingInt(Weight::getWeight));
Run Code Online (Sandbox Code Playgroud)

  • 如果你知道事先设置的可能值,那么只使用"int"比较器的减法就可以了,这样你就可以保证差异真正适合`int`范围并且不会溢出.既然你不知道OP的权重属性有哪些限制,那么这里就不合适了. (5认同)
  • [Java Integer compareTo() - 为什么使用比较与减法?](/sf/ask/191015541/) (2认同)