在 Java 中,栈(Stack)是一种重要的线性数据结构,遵循“后进先出”(LIFO, Last In First Out)的原则,即最后入栈的元素将最先被出栈,栈在算法实现、表达式求值、函数调用管理、括号匹配等场景中有着广泛应用,本文将详细介绍 Java 中栈的使用方法,包括核心 API、实际应用场景、注意事项及最佳实践。

Java 核心栈实现:java.util.Stack 类
Java 提供了内置的 Stack 类,继承自 Vector,属于线程安全的动态栈实现,其核心方法如下:
入栈操作:push(E item)
push() 方法用于将元素压入栈顶,并返回该元素。
Stack<Integer> stack = new Stack<>(); stack.push(10); // 栈底 [10] stack.push(20); // 栈底 [10, 20] stack.push(30); // 栈底 [10, 20, 30]
出栈操作:pop()
pop() 方法用于移除并返回栈顶元素,若栈为空则抛出 EmptyStackException。
Integer top = stack.pop(); // 返回 30,栈变为 [10, 20]
查看栈顶元素:peek()
peek() 方法仅获取栈顶元素而不移除,若栈为空同样抛出 EmptyStackException,常用于检查栈顶数据而不修改栈结构。
Integer currentTop = stack.peek(); // 返回 20,栈仍为 [10, 20]
判断栈是否为空:empty()
该方法返回布尔值,判断栈中是否无元素。
boolean isEmpty = stack.empty(); // 返回 false
搜索元素:search(Object o)
search() 方法返回元素在栈中的位置(从 1 开始计数,栈顶为 1),若元素不存在则返回 -1。

int position = stack.search(10); // 返回 2(10 位于栈底,从栈顶数第二个)
栈的常见应用场景
括号匹配验证
栈的经典应用之一是检查括号是否成对匹配(如 、[()]),遍历字符串时,遇到左括号则入栈,遇到右括号则检查栈顶左括号是否匹配,匹配则出栈,否则不合法,遍历结束后,若栈为空则说明完全匹配。
public boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty()) return false;
char top = stack.pop();
if (c == ')' && top != '(') return false;
if (c == ']' && top != '[') return false;
if (c == '}' && top != '{') return false;
}
}
return stack.isEmpty();
}
函数调用栈与递归
JVM 的内存管理中,每个线程都有一个“虚拟机栈”(JVM Stack),用于存储方法调用时的局部变量、操作数栈等信息,递归本质上就是利用栈的特性:每次递归调用时,当前方法的状态(参数、局部变量)压入调用栈,递归返回时再依次弹出。
计算阶乘的递归方法:
public int factorial(int n) {
if (n == 1) return 1;
return n * factorial(n - 1); // 每次调用都会将当前 n 和返回地址压栈
}
表达式求值与转换
-
后缀表达式(逆波兰表达式)求值:使用栈存储操作数,遇到运算符时弹出两个操作数计算,结果压栈,表达式
3 4 + 2 *的计算过程:- 遇到 3、4 压栈(栈:[3, 4]);
- 遇到 ,弹出 4 和 3,计算
3+4=7压栈(栈:[7]); - 遇到 2 压栈(栈:[7, 2]);
- 遇到 ,弹出 2 和 7,计算
7*2=14压栈(栈:[14]),最终结果为 14。
-
中缀转后缀:通过栈处理运算符优先级,确保表达式转换正确。
a + b * c转换为a b c * +。
Stack 类的注意事项
线程安全问题
Stack 继承自 Vector,其所有方法(如 push、pop)均被 synchronized 修饰,是线程安全的,但在单线程场景下,同步操作会带来不必要的性能开销,若无需线程安全,推荐使用 LinkedList 或 ArrayDeque(后文将介绍)。

继承自 Vector 的潜在问题
Vector 是基于动态数组实现的,Stack 继承了其扩容机制:当容量不足时,会重新分配数组(默认容量翻倍)并复制元素,可能导致性能波动。Stack 暴露了父类 Vector 的方法(如 add(index, element)),允许直接操作栈中间元素,破坏了栈的“后进先出”特性。
Stack<Integer> stack = new Stack<>(); stack.push(1); stack.push(2); stack.add(0, 3); // 直接在栈底插入 3,栈变为 [3, 1, 2],违反 LIFO 规则
更优的替代方案:ArrayDeque
Java 6 引入了 ArrayDeque(基于循环数组实现的双端队列),它实现了 Deque 接口,既可作为队列使用,也可作为栈使用,相比 Stack,ArrayDeque 具有以下优势:
- 非线程安全:单线程下性能更高(无同步开销);
- 扩容机制更优:初始容量为 16,扩容时按 2 倍增长,减少内存浪费;
- API 更符合栈语义:提供
push()、pop()、peek()等方法,且不会暴露破坏数据结构的方法。
使用示例:
Deque<Integer> stack = new ArrayDeque<>(); // 使用 Deque 接口编程,提高灵活性 stack.push(10); stack.push(20); stack.push(30); System.out.println(stack.pop()); // 输出 30 System.out.println(stack.peek()); // 输出 20
栈的最佳实践
- 优先选择
ArrayDeque:在不需要线程安全的场景下,ArrayDeque是比Stack更好的栈实现,性能和内存效率更优。 - 避免直接操作栈中间元素:即使使用
Stack,也应通过push、pop等核心方法操作,避免使用Vector的随机访问方法。 - 处理空栈异常:调用
pop()或peek()前,应通过empty()或isEmpty()检查栈是否为空,或使用try-catch捕获EmptyStackException。 - 明确栈的容量需求:若元素数量可预估,可通过
ArrayDeque的构造方法指定初始容量,减少扩容次数。Deque<Integer> stack = new ArrayDeque<>(100);。
栈作为基础数据结构,在 Java 中可通过 Stack 或 ArrayDeque 实现。Stack 虽然简单易用,但因其线程安全和继承设计的问题,在现代开发中逐渐被 ArrayDeque 替代,理解栈的核心原理、应用场景及最佳实践,有助于在算法设计、系统开发中灵活运用栈解决实际问题,提升代码的效率和可维护性,无论是括号匹配、表达式求值,还是递归算法,栈都发挥着不可替代的作用,是开发者必须掌握的工具之一。



















