【问题标题】:Why methods like sort in Collections class are not in List interface?为什么 Collections 类中的 sort 等方法不在 List 接口中?
【发布时间】:2020-10-14 12:10:54
【问题描述】:

Class Collections 有一些静态方法作为操作 List 等集合的实用程序。例如排序方法(Collections.sort(list))。我不明白为什么 Java 规范创建了另一个类来承载排序方法(以及所有其他方法,如 binarySearch)而不是 List 接口和具体的子类,如 ArrayList 和 LinkedList 实现这些方法。

更新

当我进行全球研究并阅读这篇文章的答案时,我不得不说(鸟瞰图): 有人(我提到@dan,@WJS,@cdalxndr)在这篇文章中说,以sort方法为例,因为ArrayList和LinkedList的排序可以用相同的方式完成,所以我们可以实现一次写入。所以(我说)我们可以将代码放在 List 接口中,但是在 Java 7 之前,我们不能将任何实现放在接口的主体中,并且有一次编写它的唯一方法是在实用程序类中实现。但是由于 Java 8 接口具有“默认”方法的特性。 Java 团队利用这个特性在接口级别实现了排序方法,并且该方法可以被 ArrayList 和 LinkedList 使用(默认情况下,如果类不覆盖它)

【问题讨论】:

  • @Steyrix 你误解了 Federicos 的评论。他并不是说 List 不是集合,而是说 Set 不是 List,因此当 sort() 只存在于 List 接口面时,需要它们自己的声明和实现。
  • @FedericoklezCulloca 是的,很抱歉造成误解,我错过了您评论的意思:)
  • @Steyrix 这个问题是关于静态类Collections 而不是接口Collection。对集合操作的实用方法是问题的主题,继承不适用于此处。
  • @dan 好吧,我当时也误解了问题。但是,它仍然是关于称为多态性的 OOP 概念
  • 不是每个 Collection 都可以排序,所以强制一个无法排序的 Collection 实现 sort() 方法可能被认为是不直观的设计。您当然可以指向 add() 方法,并且存在不支持添加的集合,并且该方法的实现只会抛出 unsupportedOperationException。我想最终它的设计选择是保持接口功能并且只有大多数实现也支持的方法,这样你就不会在所有方法中的 50% 只抛出异常的地方进行分类。

标签: java collections


【解决方案1】:

在 Java 8 之前,接口不能有 defaultstatic 方法,因此,添加到接口的每个方法都需要由实现该接口的所有类实现。即使您在支持类中提供了有用的实现,实现类也至少需要一个委托方法。

因此,除非您想强制所有实现从提供这些方法的某个抽象基类继承,否则您必须小心添加到接口中的内容。

即使使用default 方法,您也必须小心,避免使用太多方法污染实现类的名称空间。这也可能是 Java 8 中并非所有操作都被改进为 default 方法的原因:

虽然将default 方法添加到接口的侵入性较小,因为它不需要在现有的实现类中实现它,但它仍然可能导致与实现类的具体方法发生冲突' t 实现之前版本的接口方法。

想象一下,在 Java 8 时代之前的自定义 List 实现提供了一个有用的 void sort(Comparator c) 实例方法,只是委托给 Collections.sort(this, c);。这在 Java 8 之前有效,没有提高性能,但允许编写 list.sort(c);。如今,此方法会无意中覆盖具有相同名称和类型的 default 方法,Collections.sort 将委托给该方法,从而产生无限循环(或者更确切地说,递归)。

由于直接的好处,sort 方法已添加到List 接口中。与Collections 实用程序类中的static 方法不同,default 方法可以被覆盖。这已针对最常用的列表类型ArrayListVectorArray.asList(…) 返回的实现完成。由于所有这些实现都由数组支持,因此覆盖方法可以直接使用支持数组委托给Arrays.sort,而default 实现将使用列表内容的临时副本。

还值得注意的是,Collections 中的那些方法似乎最初是基于这些算法适用于所有类型的实现的假设,但事实并非如此。引入 Collection API 之后的两个版本,引入了 RandomAccess 标记接口,以区分两个根本不同的列表实现类别,因此静态方法可以分支到基于它的两种替代算法。

每当我们必须基于我们正在操作的类进行分支时,我们可以质疑抽象并说我们可能会更好地使用类型本身的可覆盖方法,但如上所述,存在历史原因设计,仍然有理由要小心向接口添加方法。

【讨论】:

  • 这个答案的方向是正确的,尽管它本质上与并考虑到已经对这个问题做出的所有贡献。因此,它实际上是已经提供的答案的(良好)组合,并且使主题更进一步。但我有一个问题。我不明白为什么自定义 List 中的 override it sort 方法会导致 Java 8 版本出现问题。会产生怎样的无限循环?
  • 考虑this example,它在 Java 5、6 和 7 以及早期的 Java 8 版本中运行良好。但现在失败了,因为它无意中覆盖了在 Java 8 之前不存在的 default 方法。
  • default sort 方法的另一个问题发生在一个真实的类似:Java8 Collections.sort (sometimes) does not sort JPA returned lists。但正如开发人员所知,every change breaks someone's workflow...
  • 当我将它放入我的 Netbeans 并且 JDK 为 8 或更高版本时,它会发出警告。
  • 它就在那里,“……Collections.sort 将委托给它……
【解决方案2】:

排序是一种算法,List 是一个容器。

开发人员希望将列表算法(排序、二进制搜索等)与容器逻辑(添加、删除等)分开,因此他们将算法放入实用程序类 Collections

【讨论】:

  • 男性能感觉到这种解释,但正如@kiselica-aldin 所说,自 Java 8 以来 List 接口中添加的排序方法!!!
  • @nonlinearly List.sort 是一种方便的方法,在后台它们都使用来自Arrays.sort的相同实现
  • 我同意这个答案——想想策略模式。
【解决方案3】:

从 Java 8 开始有某种选项,只需要您定义(或使用预定义的比较器)。

下面的虚拟示例:

    List<Integer> list = new ArrayList();
    list.addAll(List.of(1,5,4,3,6,8,9,2));

    list.sort(Comparator.naturalOrder());

但显然,作为用户,它与在 Collections util 中的方式不同(尽管我确实相信 Collections 实现也使用此类比较器或类似的东西进行环绕)。

【讨论】:

  • 是的...我正在查看 Java 7 文档...但是对于涉及 List 接口的 Collections 静态类的其他方法(例如 binarySearch),这个问题仍然有意义。为什么不在列表中。它的作用是什么?
  • 你也可以使用list.sort(null)按自然顺序排序,但与list.sort(Comparator.naturalOrder())相比,你失去了类型安全性,即当列表的类型不可比较时不会出现编译器错误。这与使用Collections.sort(list, null) 而不是Collections.sort(list)Collections.sort(list, Comparator.naturalOrder()) 相同。所以Collections.sort(list) 是一种与list.sort(null) 相同的便捷方法,但具有类型安全性。它们最终会使用相同的实现代码。
【解决方案4】:

因为没有必要。以下所有扩展Collection

BeanContext, BeanContextServices, BlockingDeque<E>, BlockingQueue<E>,<br>
Deque<E>, EventSet, List<E>, NavigableSet<E>, Queue<E>, Set<E>,<br>
SortedSet<E>, TransferQueue<E>

所以您提倡的是每个接口都为Collections 中的每个方法提供完全相同的实现,或者可能是一种方便的方法。恕我直言,那将是一种代码膨胀的形式。在前一种情况下,如果发现改进,则需要更改多个实现。

我知道他们在List 接口中添加了sort,可能是因为这是一个常见的要求。但我从来没有遇到过使用Collections.sort 进行排序的问题。可能还有其他我不知道的原因。

但我倾向于认为CollectionsMath 类非常相似。 DoubleInteger 没有重复的数学函数。与Math 类似,Collections 是一个utility,它提供了可供相关类使用的各种静态方法。当然,Math 类不在 Double 或 Integer 等 hierarchy 中,但它的用途非常相似。

【讨论】:

  • 原因确实是不能写list.sort(…),但正如my answer 中解释的那样,接口方法可以被覆盖(并且一直是,对于经常使用的类型)。这种性能优势被认为非常重要,甚至Collections.sort(…) 也被改装为委托给list.sort(…) 以获得相同的优势(尽管这并非没有危险,正如我的回答中所解释的那样)。
【解决方案5】:

Collections Framework(主要是在Sun Microsystems 工作时的Josh Bloch)的设计者与 Java API 的许多其他部分一样,着眼于该语言的寿命。 Java 将 API 的维持和演变的概念直接结合到文档支持的语法中,并带有 @deprecated 标签。 具体来说,他们预计可能会在多年后开发出更多的排序和搜索算法,然后才能发现更多的“容器”操作以添加到CollectionList 接口中。

他们非常清楚界面的更改会要求开发团队处理升级要求。升级要求减缓了 Sun Microsystems 希望团队能够轻松接受的修复和改进的采用。将用于排序和搜索的新方法合并到界面中将大大阻碍该目标;给选择升级到 Java 语言最新版本的团队带来了不必要的负担。相反,他们明智地选择将这些实用程序方法分流到静态实用程序类。

与 Java 8 的 List.sort(..) 一样,我只能猜测 Oracle 感受到了行业和其他语言的压力,将这些常见操作直接纳入其集合本身。但是,将这些实用程序与集合分开可以最大限度地减少它们对直接实现其接口的人的采用要求。

【讨论】:

  • 与 Java 8 的 List.sort(..) 一样,我认为这不仅仅是压力……因为 Java 8 接口有一个称为“默认”的特性。所以我们可以默认实现一个方法,所有实现这个接口的类都可以使用这个方法。因此,我们不必使用或创建具有通常应该驻留在非实用程序类中的方法的实用程序类
  • @nonlinearly 你错过了我的意思。假设您的团队创建了java.util.Set 的实现并且您使用JDK8。然后假设在JDK9中他们添加了非默认方法Set.reduce(Comparator)。为了让您的团队升级到 JDK9,您必须响应新方法,并且在您这样做之前您的项目不会编译。这是他们强加给你的团队的工作。他们希望通过将这些实用程序方法放入静态类中来最小化这种情况,这样 Java 团队的升级就会更加简单。
  • 其次,java.util.Collections 早在 default 用于接口之前就出现了。这是很久以前做出的设计决定。
  • sort添加到列表界面的原因不是“行业和其他语言的压力”,而是真正的实际好处(除了可以写list.sort(…))。正如my answer 中所解释的,接口方法可以被覆盖(并且对于经常使用的类型已经被覆盖)。这反过来解释了为什么没有添加其他方法。这并没有直接的好处,也有类似的影响。
  • 我不认为命名算法是一种优势。这意味着当存在更好的算法时,应用程序不再从更新 jdk 中受益,因为它们将继续使用旧算法。与this answer 相比,Arrays.sort 确实命名了算法,虽然只在文档中,所以它并没有妨碍优化,但结果仍然是一个坏主意,因为文档不再与实现匹配。
猜你喜欢
  • 1970-01-01
  • 2020-12-31
  • 1970-01-01
  • 1970-01-01
  • 2015-03-31
  • 2014-07-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多