为什么无效比较不会导致 Collections.sort 崩溃?



考虑以下compareTo方法,实现Comparable<T>接口

@Override
public int compareTo(MyObject o)
{
    if (o.value.equals(value)
        return 0;
    return 1;
}

令人惊讶的是,程序员实现了compareTo,就好像它是equals()一样。显然是个错误。我预计这会导致Collections.sort()崩溃,但事实并非如此。相反,它只会给出一个任意的结果:排序的结果取决于初始排序。

public class MyObject implements Comparable<MyObject>
{
    public static void main(String[] args)
    {
        List<MyObject> objects =
                Arrays.asList(new MyObject[] {
                        new MyObject(1), new MyObject(2), new MyObject(3)
                });
        Collections.sort(objects);
        System.out.println(objects);
        List<MyObject> objects2 =
                Arrays.asList(new MyObject[] {
                        new MyObject(3), new MyObject(1), new MyObject(2)
                });
        Collections.sort(objects2);
        System.out.println(objects2);
    }
    public int  value;
    public MyObject(int value)
    {
        this.value = value;
    }
    @Override
    public int compareTo(MyObject o)
    {
        if (value == o.value)
            return 0;
        return 1;
    }
    public String toString()
    {
        return "" + value;
    }
}

结果:

[3, 2, 1]
[2, 1, 3]

我们能为compareTo的这个奇怪的实现想出一个用例吗?或者它总是无效的。在后者的情况下,它应该抛出异常,或者甚至不编译吗?

它没有崩溃或引发异常的原因。

当你实现这个方法时,你必须履行合同,但如果你不履行,这只意味着你会从任何依赖它的东西中得到任意的结果。没有什么会特意检查你的实现的正确性,因为这只会减慢一切。

排序算法的效率是根据它进行的比较次数来定义的。这意味着它不会仅仅为了检查您的实现是否一致而添加额外的比较,就像HashMap会对所有内容调用.hashcode()两次,只是为了检查两次都给出相同的结果一样。

如果在排序过程中发现问题,则可能引发异常;但不要依赖它。

违反ComparableComparator的合同不一定会导致异常。sort方法不会花费额外的精力来检测这种情况。因此,它可能会导致顺序不一致、结果明显正确或引发异常。

实际行为取决于输入数据和当前实现。例如,Java 7在其sort实现中引入了TimSort,这比早期Java版本的实现更有可能为不一致的ComparableComparator实现抛出异常。这可能会发现在使用以前的Java版本时仍然未检测到的错误,然而,这并不是一个有助于调试的功能,它只是更复杂的优化的副作用。


请注意,对于像您的问题中这样的简单情况,编译器或代码审计工具检测比较方法的不对称行为并非完全不可能。然而,据我所知,还没有编译器自动执行这样的检查。如果您希望安全起见,您应该始终为实现ComparableComparator的类实现单元测试。

根据文档,Collections.sort()使用了一种合并排序的变体,它将列表划分为多个子列表,然后重复合并这些列表;排序部分是在这些列表的合并期间完成的;如果您的比较方法是任意的,那么将以任意的顺序进行合并。

因此,每个元素都比所有其他元素都大。

根据你想要表示的数学组或顺序,它可能是一个有效的情况,因此没有理由抛出任何错误。然而,您展示的示例并不能代表您所知道的标准数字的自然顺序。

提交的订单不是全部订单。

===编辑===

感谢您的评论,事实上,规范不允许使用compareTo方法来实现非完全或非反对称阶。

相关内容

最新更新