【问题标题】:Why can Collections.sort take no comparator but List.sort must take a comparator?为什么 Collections.sort 可以不带比较器,但 List.sort 必须带比较器?
【发布时间】:2020-07-28 01:11:24
【问题描述】:

我不明白为什么 List.sort() 没有没有比较器的版本。

我特别看到可以使用 null:
list.sort(null) 调用 List.sort,而且它似乎使用自然顺序进行排序。

我注意到在我的 IDE Collections.sort() 调用 List.sort(null),所以我想知道为什么 List.sort 似乎最近在 Java 8 中引入了没有没有比较器的简单版本,这在很多情况下是不需要的。

此外,我不确定在这种情况下是否更好地调用 List.sort(null) 以避免额外的调用,或者是否仍然更喜欢调用 Collections.sort() 并避免丑陋的 null 参数。

【问题讨论】:

  • @Slaw 哈哈,在删除该评论之前阅读该方法的 javadocs。这让我觉得 OP 当然可以简单地调用list.sort(null)。我不会错过list.sort() 过载。
  • @ernest_k 首先允许null 或无参数重载的唯一问题是中断类型安全。当然,按自然顺序排序是一种极其常见的情况,因此牺牲类型安全性而不是一直指定 Comparator.naturalOrder() 是值得的(我知道我倾向于在适当的时候通过 null)。跨度>
  • @Slaw 同意。我认为造成差异的原因是 list.sort(Comparator<T>) 是在 Java 8 中引入的(即编译时的泛型和类型安全);而Collections.sort() 是在Java 5 之前引入的。我还认为允许null 作为list.sort 的参数是出于适应向后兼容性的需要(不要在List API 中将其推得太远)。
  • @ernest_k 这是有道理的。我相信 Holger 对流中的排序提出了类似的案例:stackoverflow.com/a/47867848/6395627

标签: java list sorting comparator comparable


【解决方案1】:

tldr

这是一个设计决定,混合了一些历史原因。可以添加list.sort(),但它不能确保编译时的安全,只有运行时。对于 JDK 来说,这将是一个相当奇怪和不寻常的设计。


Collections#sort

Collections,因为可以在方法声明中指定类型边界,所以有可能强制Comparable元素:

public static <T extends Comparable<? super T>> void sort(List<T> list)

所以该方法可以确保集合包含Comparable 元素。所以不需要Comparator


列表#排序

List 不能这样做。它的泛型类型必须允许一切,你必须能够使用List&lt;Dog&gt;,尽管那些可能不是Comparable

因此,如果存在list.sort(),那么此方法需要确定列表泛型类型T 是否为Comparable。但是这些信息只存在于运行时,但我们希望它在编译时得到一个好的设计。

因为这会导致糟糕的设计,list.sort() 不存在。因此,强制使用Comparator 的额外方法可以确保编译时的安全性..


list.sort(null)

使用list.sort(null) 代替list.sort() 显然是一种设计选择,我认为这是一个不错的选择。如前所述,List 不可能在编译时确保安全。所以唯一的选择是为了运行时安全,这不是很好。

拥有一个只有有时有效的list.sort(),否则会引发异常,否则将是一个奇怪的设计选择。然而,像list.sort(null) 这样的电话对任何用户来说都更加清晰。尤其是,更清楚的是,这将被运行时决策覆盖,而不是编译时检查。

您可能更希望将其明确化并使用自然排序的琐碎Comparator

list.sort(Comparator.naturalOrder());

这对读者来说至少会更清楚。


历史

您现在可能想知道为什么 List.sort(null) 甚至是受支持的功能,以及为什么它们不要求您始终明确地给出 Comparator。我无法调查开发人员的想法,但我怀疑是历史原因。 JDK中有不少类似的例子,尤其是排序方面。

这主要是因为 Java 保持了向后兼容性。 Java 5 引入了泛型,因此之前存在的所有处理容器的东西都没有考虑到类型安全。

有几个高度相关的例子:

  • Arrays#sort(Object[])(从 Java 1.2 开始)不强制 Comparable。因此,他们不得不在 Java 5 Arrays.sort(T[], Comparator) 中添加一个额外的重载,就像 List 示例一样。
  • 方法Collections.sort(T[], Comparator) 实际上接受null 作为比较器。一个糟糕的设计选择,但它是在当时还没有泛型时做出的。所以他们不能再删除这个功能了。
  • 有趣的是,他们决定让List.sort(Comparator),一种新方法,也支持null。然而,在Stream.sort(Comparator) 他们没有。这是 JDK 中奇怪的不一致。

【讨论】:

    【解决方案2】:

    当您不提供比较器时,将使用 natural 排序,这仅适用于实现 Comparable 的类型。

    如果您查看Collections.sort() 的方法定义:

        public static <T extends Comparable<? super T>> void sort(List<T> list)
    

    元素类型的作用域是它必须实现Comparable

    List 没有这种约束,因为在很多情况下,您希望列表保存没有自然顺序的事物。因此,您不能调用sort(),因为List 不能保证它包含具有自然顺序的元素。

    你可以测试一下:

    以下是编译错误:

        List<Object> objs = new ArrayList<>();
        objs.add(new Object());
        objs.add(new Object());
    
        Collections.sort(objs);
    

    因为Object 没有实现Comparable&lt;Object&gt;,所以除非您提供比较器,否则没有明智的方法对它们进行排序。

    如果我绕过 Collections 类并这样做:

        List<Object> objs = new ArrayList<>();
        objs.add(new Object());
        objs.add(new Object());
    
        objs.sort(null);
    

    我得到一个例外,解释相同:

        java.lang.ClassCastException: class java.lang.Object cannot be cast to class java.lang.Comparable
    

    【讨论】:

    • 很好的解释和展示。但我认为它忽略了解释为什么list.sort(null) 是一个支持的功能(参见它的Javadoc)以及如果他们已经允许list.sort(null),为什么他们不选择list.sort()。不确定,但我认为这是 OP 的重点。
    【解决方案3】:

    我不明白为什么 list.sort() 没有没有比较器的版本的逻辑?

    这也是我不明白的地方,但我想这只是介绍该方法的程序员的个人喜好。

    由于 Java 1.8 支持接口中的默认方法,因此很容易添加一个不带参数 List.sort() 的重载版本,以明确该参数是可选的。该方法可以简单地委托给sort(Comparator) 方法。

    我通常会引入重载方法来明确哪些参数是可选的,哪些不是。

    我的简单规则是:

    • 一个方法指定一个参数,因为它需要它。因此默认情况下它是强制性的。
    • 如果该参数是可选的,则提供不带该参数的重载方法。

    另外我不确定在这种情况下最好调用 list.sort(null) 以避免额外的调用,或者它仍然是首选 collection.sort() 并避免丑陋的 null 参数。

    我不会担心这一点,因为如果那一个额外的电话是您的问题,那么您就真的有麻烦了。所以选择一个你认为更容易理解的版本。 JIT 编译器也有可能通过内联删除调用。

    【讨论】:

      猜你喜欢
      • 2018-11-27
      • 2012-12-20
      • 1970-01-01
      • 2016-02-14
      • 1970-01-01
      • 2019-01-13
      • 2016-02-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多