使用Java 8集合流API实现堆栈

29

我有一个方法,每次执行它时会生成一个对象,并且我需要反转获取这些对象的顺序。所以我想自然的方式是使用栈,因为后进先出。

然而,Java Stack 似乎与新的 Java 8 流 API 不兼容。

如果我这样做:

   Stack<String> stack = new Stack<String>();
   stack.push("A");
   stack.push("B");
   stack.push("C");

   List<String> list = stack.stream().collect(Collectors.toList());
   System.out.println("Collected: " + list);

我得到的输出是:

Collected: [A, B, C]

为什么它没有按照预期的后进先出顺序将它们输出到流中?这是将所有项目以正确的(后进先出)顺序从堆栈刷新到列表的正确方法吗?


我认为这是因为Stack扩展了Vector(它维护原始插入顺序;pop方法返回Stack中的最后一个对象)。如果使用Deque,你会得到相同的结果吗?例如,ArrayDeque从前面推送和弹出,因此它应该给你想要的顺序。 - azurefrog
Deque(以及其实现类)甚至没有 stream() 方法。问题不在于我是否可以解决它(我显然可以编写自己的循环)。问题是为什么 Stack 提供了流方法并以错误的方式进行流,当我们期望使用所选集合的 LIFO 排序时。 - jbx
1
根据ArrayDeque API,它具有与Stack相同的stream()方法,来自java.util.Collection。但是我这里没有Java 8,所以无法测试它。 - azurefrog
啊,我不小心使用了JDK 7版本。所以,是的,使用Deque它可以正常工作!为什么会这样呢?(我只是将Stack更改为Deque - jbx
1
因为Stack只是一个奇怪的突变Vector。实际上,我认为在调用stream()时打破直觉排序可能是你想要添加到https://dev59.com/EnE85IYBdhLWcg3wJQAa的内容。 - azurefrog
我之前没有意识到这一点。他们应该彻底弃用这些基于Vector的类。我已经使用LinkedListArrayDeque进行了测试,如果我使用push()方法添加项目,那么stream()方法将正确地反转它们的顺序。 - jbx
1个回答

56

正如评论中已经提到的那样,我们有经过充分测试的Deque接口,应该优先考虑使用。

但我会告诉你为什么不应该使用Stack

首先,Java Doc. 中的 Stack 说:

A more complete and consistent set of LIFO stack operations is provided by the Deque interface and its implementations, which should be used in preference to this class. For example:

Deque<Integer> stack = new ArrayDeque<Integer>();

请查看JavaDoc

那么Stack类有什么问题呢?

就像Martin Fowler在他的书中所提到的那样,使用重构方法Replace Inheritance with Delegation时,栈不应该继承自向量。

不适当的继承的经典例子之一是将栈作为向量的子类。 Java 1.1在其实用程序中这样做(淘气的男孩!)[6,p.288]

相反,他们应该像下面的图片一样使用委托,这也是来自该书的内容。

还可以在此处查看:Replace Inheritance with Delegation

Replace Inheritance with Delegation

那么,为什么这是个问题:

因为堆栈只有5个方法:

  1. pop
  2. push
  3. isEmpty
  4. search
  5. size

size()isEmpty()是从Vector类继承而来的,而Vector的其他方法没有被使用。但是通过继承,其他方法被转发到了Stack类中,这是没有意义的。

对于这个问题,Fowler提出了以下建议:

你可以忍受这种情况,并使用惯例来表示,尽管它是一个子类,但它只使用了超类的一部分功能。但是这会导致代码表达了一件事情,而你的意图是另一件事情——这是一个你应该消除的混淆。

这违反了接口隔离原则

该原则指出:

客户不应被强制依赖于他们不使用的接口。


你可以查看 VectorStack 类的源代码,你会发现 Stack 类继承了 spliterator 方法和 VectorSpliterator 内部类,这些都来自于 Vector 类。
这个方法被 Collection 接口用来实现 stream 方法的默认版本:
default Stream<E> stream() {
  return StreamSupport.stream(spliterator(), false);
}

因此,避免简单地使用VectorStack类。

[6] 重构:改善现有代码的设计,Martin Fowler著,1997年。


1
感谢您的详细解释。 - jbx
2
另一个重大的不同之处在于,Vector / Stack(我认为所有旧的java.util.*集合)是线程安全的。这意味着在单线程上下文中,它们仍然被同步访问,这比非同步访问慢得多。基本上,这与StringBuffer被StringBuilder“替换”的原因相同。 - Angel O'Sphere

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