使用Java 8集合流API进行堆栈

jbx*_*jbx 19 java java-8 java-stream java-collections-api

我有一个方法,每次执行它时都会生成一个对象,我需要颠倒我获取它们的顺序.所以我认为这样做的自然方式就是Stack,因为它是LIFO.

但是,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);
Run Code Online (Sandbox Code Playgroud)

我得到的输出是:

Collected: [A, B, C]
Run Code Online (Sandbox Code Playgroud)

为什么不以预期的LIFO顺序将它们输出到流中?这是将所有项目从堆栈清除到右(LIFO)顺序列表的正确方法吗?

Zel*_*don 34

正如评论中已经提到的,我们有一个经过良好测试的Deque界面,应该是首选.

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

首先,Java Doc.堆栈说自己:

Deque接口及其实现提供了更完整和一致的LIFO堆栈操作集,应优先使用此类.例如:

Deque stack = new ArrayDeque();

请参阅JavaDoc.

这就是Stack课堂上的问题.

就像Martin Fowler在他的" 重构:改进现有代码的设计"一书中已经提到的那样 ,在重构方法中将继承替换为委托,Stack不应该从Vector继承.

不适当继承的一个典型例子是使堆栈成为vector的子类.Java 1.1在它的实用程序(顽皮的男孩!)中做到了这一点[6,p.288]

相反,他们应该使用如下图所示的委托,这也来自书中.

另请参见:使用委派替换继承

将继承替换为委派

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

因为Stack只有5个方法:

  1. 流行的
  2. 是空的
  3. 搜索
  4. 尺寸

    size()并且isEmpty()是从Vector类继承的,其他方法Vector都没有使用过.但是通过继承,其他方法被转发到Stack没有意义的类.

福勒对这个问题说:

你可以忍受这种情况并使用约定来说虽然它是一个子类,但它只使用了超类函数的一部分.但是这导致代码在你的意图是其他东西时说了一件事 - 你应该删除的混乱.

这会损害接口隔离原则

其中说:

客户不应强制依赖他们不使用的接口.


您可以查看VectorStack的源代码,您将看到Stack类从类继承spliterator方法和VectorSpliteratorinnerClass Vector.

这个方法被Collection接口用于impl.stream方法的默认版本:

default Stream<E> More ...stream() {
  return StreamSupport.stream(spliterator(), false);
}
Run Code Online (Sandbox Code Playgroud)

所以要避免简单地使用VectorStack类.

[6]重构:改进现有代码福勒的设计,Martin 1997年

  • 另一个很大的区别是 Vector/Stack(我认为所有旧的 java.util.* 集合)都是线程安全的。这意味着在单线程上下文中它们仍然是同步访问的,这比非同步访问慢得多。StringBuffer被StringBuilder“取代”的原因基本上是同样的“问题”。 (2认同)