首先,在Stack的文档介绍中说:
Deque 接口及其实现提供了一组更完整和一致的 LIFO 堆栈操作,应优先使用此类。
这告诉我们Stack 类主要是一个剩余部分,随着更新的Java 集合框架,它或多或少变得多余。
其次,“提供相同的功能”并不是衡量一个类是否应该存在的好方法。虽然LinkedList 提供了创建堆栈所需的所有操作,但它的性能会很差。链表适用于在随机位置插入和删除元素。在堆栈中,我们只在末尾追加或删除,这使得 ArrayList 对实现堆栈更具吸引力。
此时,我们意识到ArrayList 和LinkdList 都提供了List 的功能,这让我们更接近良好的面向对象设计的核心:
- 我们在一个接口中定义了一组操作(例如
List)。
- 我们提供该接口的一个或多个实现,它们必须都提供所需的功能,但可以针对特定用例(例如
ArrayList 或LinkedList)进行优化。
- 如果某个使用模式出现得特别频繁,我们可能会决定添加另一个在其名称中引用此模式的类,这将使代码结构更好。例如,我们可以通过简单地委托到
ArrayList来实现Stack,但提供一个在其名称中清楚地说明其含义并且不提供操作的类型(如随机访问)可能违反堆栈的概念。 (这不是 java.util.Stack 所做的,这让我们回到了文档中的引用。)
请注意,List 和较新的Deque 接口之间的继承关系比List 和Stack 之间的继承关系更加一致。 LinkedList 实现了 Deque,这是正确的,因为 Deque 要求可以从 / 到开头或结尾添加和删除元素,而 LinkedList 通过在随机位置提供插入和删除来满足此要求。另一方面,Stack 实现了 List,这应该被认为是有问题的。
后期更新:因为我对 “链接列表有利于在随机位置插入和删除元素的说法表示反对。在堆栈中,我们只在末尾追加或删除,这使得 ArrayList 对实现堆栈更具吸引力。”,我想对此进行扩展。
链表允许在恒定时间内在任意位置插入和删除元素。另一方面,在给定索引的情况下,找到一个元素需要线性时间。当我说它们适合在随机位置插入和删除时,我的意思是由迭代器给出的位置,而不是索引。如果给定索引,则插入和删除对于链表和数组都是线性时间操作,但链表的常数因子会高得多。
基于数组的列表允许在末尾进行分摊的常量时间插入和删除,以及按索引进行常量时间访问。在随机位置添加和删除元素是线性时间操作,无论位置是由索引还是由迭代器(基本上只是数组的索引)给出。
在堆栈实现中,链表的唯一优点——它能够在恒定时间内在任意位置(由迭代器给出)插入和删除元素——是不需要的。另一方面,它的内存开销要高得多,而且它的内存访问也远不如连续数组。鉴于在列表末尾添加和删除项目的渐近复杂度在任何一种情况下都是摊销常数,因此基于数组的列表是实现堆栈存储的更好选择。
更好的数据结构是可变数量的固定大小的缓冲区,通过指针链接在一起。这样的数据结构通常用于实现所谓的双端队列。它提供了数组的大多数优点,并且几乎没有额外的开销,并且从末尾(或开头)添加和删除不仅是摊销的,而且始终是恒定时间的操作。