在 Java 集合框架中,Stack 是一种基于后进先出(LIFO, Last-In-First-Out) 原则的线性数据结构,主要用于实现栈的相关操作。
栈结构如下图:
注意,Stack 并非独立设计,而是直接继承 Vector(Vector 是 Java 早期的动态数组实现类),本质上是通过 Vector 的动态数组机制存储元素。这意味着 Stack 天然具备 Vector 的特性,例如:动态扩容(数组满时自动增长)、支持通过索引访问元素等,但也继承了 Vector 的设计局限性(如暴露非栈操作方法)。
Stack 类支持元素的压栈(push)、出栈(pop)、查看栈顶元素(peek)等操作。
由于 Stack 直接继承自 Vector,因此底层是基于数组实现,本质上是通过动态数组来存储元素。
线程安全:由于继承自 Vector,所有方法都具有同步特性,适合多线程环境。
操作简单:提供了直观的栈操作方法,易于理解和使用。
性能问题:同步操作会带来额外的性能开销,在单线程环境中效率较低。
设计缺陷:继承 Vector 导致 Stack 暴露了所有 Vector 的方法(如 add(int, E)),破坏了栈的封装性(理论上栈只能从顶端操作)。
注意,由于 Stack 存在性能问题和设计缺陷,在实际开发中,推荐使用 Deque 接口的实现类(如 ArrayDeque)替代 Stack,原因如下:
Deque 提供了更规范的栈操作方法(push()、pop()、peek())。
ArrayDeque 是非同步的,性能优于 Stack。
封装性更好,不会暴露额外的操作方法。
例如:
import java.util.ArrayDeque; import java.util.Deque; public class DequeExample { public static void main(String[] args) { Deque<Integer> stack = new ArrayDeque<>(); stack.push(10); stack.push(20); System.out.println(stack.pop()); // 输出: 20 } }
Stack 类的主要方法如下:
push(E item) 将元素压入栈顶,返回被压入的元素。
pop() 移除并返回栈顶元素,栈为空时抛出 EmptyStackException。
peek() 返回栈顶元素(不移除),栈为空时抛出 EmptyStackException。
empty() 判断栈是否为空,为空返回 true 无。
search(Object(Object(int o) 返回指定元素在栈中的位置(从栈顶开始计数),元素不存在时返回 -1。
下面通过一个示例介绍 Stack 类的基础用法,如压栈、出栈等操作,如下:
import java.util.Stack; public class StackExample { public static void main(String[] args) { // 创建栈对象 Stack<Integer> stack = new Stack<>(); // 压栈操作 stack.push(10); stack.push(20); stack.push(30); System.out.println("栈元素: " + stack); // 输出: [10, 20, 30] // 查看栈顶元素 System.out.println("栈顶元素: " + stack.peek()); // 输出: 30 // 出栈操作 int top = stack.pop(); System.out.println("出栈元素: " + top); // 输出: 30 System.out.println("出栈后栈元素: " + stack); // 输出: [10, 20] // 判断栈是否为空 System.out.println("栈是否为空: " + stack.empty()); // 输出: false // 查找元素位置 int index = stack.search(10); System.out.println("元素10的位置: " + index); // 输出: 2(从栈顶开始计数) } }
运行结果:
栈元素: [10, 20, 30] 栈顶元素: 30 出栈元素: 30 出栈后栈元素: [10, 20] 栈是否为空: false 元素10的位置: 2
下面通过分析 Stack 源码,了解 Stack 的实现原理:
package java.util; public class Stack<E> extends Vector<E> { /** 用于JDK 1.0.2版本的序列化兼容性 */ @java.io.Serial private static final long serialVersionUID = 1224463164541339165L; /** * 创建一个空栈 */ public Stack() { } /** * 将元素压入栈顶。此操作与以下代码效果完全相同: * addElement(item) * * @param item 要压入栈的元素 * @return 被压入的元素(即参数 {@code item}) * @see java.util.Vector#addElement */ public E push(E item) { // 调用父类 Vector 的 addElement 方法,将元素添加到末尾(栈顶) addElement(item); return item; } /** * 移除栈顶的对象,并将该对象作为此方法的返回值 * * @return 栈顶的对象(即Vector对象的最后一个元素) * @throws EmptyStackException 如果栈为空时调用此方法 */ public synchronized E pop() { E obj; int len = size(); // 获取当前栈的长度 obj = peek(); // 先获取栈顶元素(会检查栈是否为空) removeElementAt(len - 1); // 移除栈顶元素(Vector的最后一个元素) return obj; } /** * 查看栈顶的对象,但不从栈中移除它 * * @return 栈顶的对象(即Vector对象的最后一个元素) * @throws EmptyStackException 如果栈为空时调用此方法 */ public synchronized E peek() { int len = size(); // 获取当前栈的长度 if (len == 0) throw new EmptyStackException(); // 栈为空时抛出异常 return elementAt(len - 1); // 返回Vector的最后一个元素(栈顶) } /** * 测试此栈是否为空 * * @return 当且仅当栈不包含任何元素时返回 true;否则返回 false; */ public boolean empty() { return size() == 0; // 通过判断 Vector 的大小是否为 0 来确定栈是否为空 } /** * 返回对象在栈中的 1-based(从1开始)位置。 * 如果对象 o 是栈中的元素,此方法返回该元素距离栈顶的距离。 * 栈顶元素的距离被视为 1。此方法使用 equals 方法来比较 o 与栈中的元素。 * * @param o 要查找的对象 * @return 从栈顶开始计算的 1-based 位置;返回值 -1 表示该对象不在栈中 */ public synchronized int search(Object o) { // 调用 Vector 的 lastIndexOf 方法,获取对象在向量中最后一次出现的索引(从0开始) int i = lastIndexOf(o); if (i >= 0) { // 转换为 1-based 的栈顶距离(栈顶为1) return size() - i; } return -1; // 未找到对象 } }
更多信息请参考 https://docs.oracle.com/javase/8/docs/api/java/util/Stack.html API 文档。