为什么Java中没有"List.reverse()"方法?

15

在Java中,要反转List中的元素,我需要使用:


Collections.reverse(list);
Collections.reverse(list)

我只是在想为什么Java没有在List接口中实现反转方法,这样我就可以像这样进行原地反转:

list.reverse()

有没有人对此有任何想法?


1
你会经常使用它吗? - wero
@wero 老实说并不是很常见。 - Hanfei Sun
我猜它存在于集合中意味着它并没有被遗忘。可能集合接口的制造者只是试图尽可能地保持这些接口的“轻量级”。 - GhostCat
@hanfeisun 自从 Collection.reverseList 同时引入(JDK 1.2)以来,设计者的意图很可能是不将其包含在 List 接口中,而仅提供一个帮助方法。 - wero
这是一个问题的特例,该问题的链接为https://dev59.com/UnA75IYBdhLWcg3w0cnx(可以通过实例化`fill`、`shuffle`和其他静态方法来提出相同的问题...) - Marco13
7个回答

11
为什么Java中没有List.reverse()方法?
因为有Collections.reverse(List)方法取而代之。
API设计者们认为强制每个List实现都实现一个99.9%的时间都不用的方法是一个坏主意。这可以通过使该方法“可选”来解决,但这也有缺点;例如运行时异常。
对于某些列表类型(例如流包装器/适配器),实现原地反转可能会带来问题。它通过要求重新实例化列表来改变列表的内存使用特性。
此外,请注意提供reverse()的泛型实现(源代码)使用set交换元素。对于标准列表类型,它接近最优。
@shmosel评论说:
我假设OP正在问为什么它没有被添加为默认方法,就像List.sort()一样。
好的观点。可能适用于99.9%的论据。请记住,这只能帮助使用Java 8或更高版本编译器等构建的代码库的人。
1-这包括您的代码库和第三方库的实现。
2-86%的统计数据是为了戏剧效果而杜撰的 :-)

1
我认为OP的问题是为什么它没有像List.sort()一样被添加为默认方法。 - shmosel

5
由于与fillrotateshuffleswap等无限可能的列表函数相同,它们没有在List接口中声明。它们不是“list”抽象的一部分;相反,它们可以在该抽象的基础上实现。
一旦List实现了已经在List接口中的方法,就可以在不需要了解特定List实现的情况下,在List抽象之上编写一个reverse函数。因此,强制每个实现List的类提供reverse(和fillrotateshuffleswap等)的自定义实现是没有意义的。

5

注意:这个问题是"为什么Collections类包含独立的(静态)方法,而不是将它们添加到List接口中?"的一个非常特定的情况 - 甚至可以认为它是重复的。除此之外,就每个单独方法背后的设计理念进行争论就像读茶叶一样,没有人能够告诉我们关于reverse方法的设计决策的"原因",直到Josh Bloch在这里发表答案为止。有趣的是,这一点并没有在Java Collections API Design FAQ中涉及...


一些其他的回答乍一看似乎很有说服力,但却引发了其他问题。特别是,有些回答根本没有给出设计决策的原因。即使有其他方法来模拟某个方法的行为,或者当一个方法在“99.9%的时间内”不被使用时,将其包含在接口中仍然是有意义的。

查看 List 接口,您会注意到基本上可以根据两个其他方法实现所有方法:

  • T get(int index)
  • int size()

(对于可变列表,您还需要 set)。这些正是在 AbstractList 中仍然抽象的方法。因此,所有其他方法都是相对“方便”的方法,可以按照规范基于这两种方法实现。在这方面,我认为 答案Sam Estep 包含了一个重要观点:可以争论要实现数十个其他方法。当然,有很多 好的理由这样做。看一下 Collections#reverse(List) 的实际实现:

public static void reverse(List<?> list) {
    int size = list.size();
    if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
        for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
            swap(list, i, j);
    } else {
        ListIterator fwd = list.listIterator();
        ListIterator rev = list.listIterator(size);
        for (int i=0, mid=list.size()>>1; i<mid; i++) {
            Object tmp = fwd.next();
            fwd.set(rev.previous());
            rev.set(tmp);
        }
    }
}

这里有什么是 REVERSE_THRESHOLDRandomAccess 的东西?说真的,如果我觉得需要引入像 RandomAccess 这样的标记接口,我会强烈质疑我的设计。每当你有一个像这样的方法:
void doSomethingWith(Type x) {
    if (x instanceof Special) doSomethingSpecial((Special)x);
    else doSomethingNormal(x);
}

如果出现strong这样的标记,那么这实际上应该是一个多态方法,应相应地为Special类型进行实现。

所以,将reverse方法引入接口是有道理的,以允许多态实现。对于fillrotateshuffleswapsort等也同样适用。同样地,可以引入静态方法,例如

Collections.containsAll(containing, others);

这个API提供了类似于Collection#containsAll方法的功能。但总的来说,设计者选择了一组特定的方法,认为它们是合适的。留出某些方法的原因之一可能可以从Java集合API的核心设计者之一Joshua Bloch在"如何设计一个好的API及其重要性"的演讲中给出的底线之一得到解释:

犹豫时,不加

有趣的是,在所有可以通过List接口中的方法实现多态(通过方法)的情况下,只有一个方法实际上以Java 8 default方法的形式出现在接口中:List#sort()。也许其他方法,例如reverse,以后会被添加...


好的回答。关于最后一部分,我怀疑它是否会被加入到界面中。sort() 很常被调用,而 reverse() 则比较罕见。并且在接口中引入一个新方法,即使作为默认方法,也会破坏已有的东西: 子类已经有了不同返回类型的 reverse() 方法。如果原贴作者确实需要这个功能,Kotlin 扩展函数提供了一个简单的解决方案。 - JB Nizet

4
因为Collection是一个实用类,它基于SOLID原则之一:
S - 单一职责原则

这个原则指出如果我们有两个理由去更改一个类,我们必须将功能拆分成两个类。

你有一个扮演某种角色的类,如果你需要操作内部数据,你需要创建一个子类来扮演另一个角色。

3
如果你需要list.reverse(),你需要使用Eclipse Collections,而当你可以使用list.reverseThis()时,请参见这里。在JDK列表中,很多方法(如排序、最大值、最小值等)未被添加。
这是两种不同的API设计方式:
  1. 集合中有很多方法->丰富的集合->Eclipse Collections,缺点:列表中有很多很少使用的方法。
  2. 仅包含最常用的方法和实用程序类->JDK集合,缺点:需要使用像Collections这样的实用程序类。

2
我相信“为什么”这个问题已经得到了回答,但是有一个要注意的地方 - List.reversed 将会在Java 21中可用(发布日期为2023年9月19日)。
System.out.println(List.of(1, 2, 3).reversed()); // [3, 2, 1]

注意:它返回此集合的逆序view

有关更多详细信息,请参阅JEP 431:序列化集合


1
所以,答案是:“没有理由!” - Basilevs

0

Reverse(反转)在Collections中被定义(带有额外的“s”)。这不是集合层次结构的一部分,而是作为实用程序类的一部分给出,可以用于不同的列表。

将列表反转并不是定义列表的关键部分,因此它被保留在接口之外并单独给出。如果在接口中定义,每个人都必须实现它,这可能并不适合所有人。

集合的制造商也可以将其构建在List层次结构中(由于大多数列表派生都有一个抽象类在中间,他们可以将其放在任何抽象类中间)。然而,为了简化每个人的生活,将其保留在单个实用程序类中是有意义的,这样我们就不必找出所有与集合相关的实用程序函数所在的类。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接