使用 Java 8 集合流 API 的堆栈

我有一个每次执行它都会生成一个对象的方法,我需要反转获取它们的顺序。所以我认为这样做的自然方法是堆栈,因为它是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);

我得到的输出是:

Collected: [A, B, C]

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


答案 1

正如评论中已经提到的,我们已经很好地测试了Deque接口,这应该是首选。

但是我会给你为什么不应该使用Stack的原因。

起初,堆栈的Java文档自己说:

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

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

请参阅 JavaDoc

那么,这是什么类的问题。Stack

就像 Martin Fowler 在他的书《重构:改进现有代码的设计,重构方法用委托替换继承》中已经提到的那样,堆栈不应该从向量继承。

不适当继承的经典示例之一是使堆栈成为向量的子类。Java 1.1在其实用程序中做到了这一点(淘气的男孩![6,第288页]

相反,他们应该像下图一样使用委派,这也是来自这本书。

另请参阅此处:将继承替换为委派

Replace Inheritance with Delegation

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

因为堆栈只有 5 个方法:

  1. 流行
  2. 是空的
  3. 搜索
  4. 大小

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

福勒对这个问题说:

你可以忍受这种情况,并使用约定说,尽管它是一个子类,但它只使用超类函数的一部分。但这会导致代码说一件事,而你的意图是另一件事 —— 你应该消除的困惑。

这损害了接口隔离原则

它说:

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


您可以查看 VectorStack 类的源代码,您将看到 Stack 类从该类继承了该方法和 innerClass。spliteratorVectorSpliteratorVector

接口使用此方法来 impl。流方法的默认版本:Collection

default Stream<E> stream() {
  return StreamSupport.stream(spliterator(), false);
}

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

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


答案 2

推荐