Java Stack 类(建议收藏)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
在 Java 编程中,栈(Stack)是一种经典的线性数据结构,其核心特性是“后进先出”(LIFO)。Java 标准库中内置了 Stack
类,它提供了对栈操作的便捷实现。然而,许多开发者对 Stack
类的具体用法、潜在问题以及实际应用场景仍存在困惑。本文将从基础概念出发,结合代码示例和实际案例,深入讲解 Java Stack 类
的核心知识点,并探讨其在不同场景下的使用技巧。
一、栈(Stack)的基本概念与特性
1. 栈的定义与比喻
栈可以想象为一个“叠盘子”的结构:每次只能从顶部添加或移除盘子。这种“后进先出”的特性是栈的核心特征。例如,当你将盘子逐个叠放时,最后一个放上去的盘子(后进)会最先被取走(先出)。
在编程中,栈的常见操作包括:
- Push:将元素压入栈顶。
- Pop:移除并返回栈顶元素。
- Peek:查看栈顶元素但不移除。
2. Java 中的 Stack 类实现
Java 的 Stack
类位于 java.util
包中,它继承自 Vector
类,并实现了 Deque
接口的部分功能。以下是其核心代码结构:
public class Stack<E> extends Vector<E> implements Cloneable, java.io.Serializable {
// 栈的具体实现方法
}
需要注意的是,Stack
类虽然功能强大,但它的线程安全性较低(继承自 Vector
的同步机制可能影响性能),因此在多线程环境下需谨慎使用。
二、Stack 类的核心方法详解
1. 常用操作方法
(1) 入栈操作:push(E element)
Stack<Integer> stack = new Stack<>();
stack.push(10); // 将10压入栈顶
stack.push(20); // 栈顶变为20
(2) 出栈操作:pop()
int poppedValue = stack.pop(); // 移除并返回栈顶元素(20)
System.out.println(poppedValue); // 输出:20
(3) 查看栈顶元素:peek()
int topElement = stack.peek(); // 查看栈顶元素(此时栈顶为10)
(4) 检查栈是否为空:empty()
if (stack.empty()) {
System.out.println("栈为空!");
}
2. 扩展方法与注意事项
(1) 获取栈的大小:search(Object element)
此方法返回指定元素距离栈顶的位置(从1开始计数),若未找到返回 -1。例如:
stack.push(30);
int position = stack.search(20); // 返回-1,因为20已被弹出
(2) 异常处理
当尝试从空栈执行 pop()
或 peek()
操作时,会抛出 EmptyStackException
。例如:
try {
stack.pop();
} catch (EmptyStackException e) {
System.out.println("栈已空,无法弹出元素!");
}
三、Stack 类与 ArrayList 的对比
1. 核心区别:数据访问模式
- Stack:仅允许对栈顶元素进行操作,严格遵循 LIFO 原则。
- ArrayList:支持随机访问和修改任意位置的元素,灵活性更高。
2. 使用场景选择
场景类型 | 推荐使用 Stack 类 | 推荐使用 ArrayList 类 |
---|---|---|
表达式求值、括号匹配 | 是 | 否 |
需要频繁访问中间元素 | 否 | 是 |
3. 性能与线程安全
- 线程安全:
Stack
继承自Vector
,其方法通过synchronized
实现线程安全,但同步开销较大。 - ArrayList:默认是非线程安全的,但可通过
Collections.synchronizedList()
包装实现线程安全。
四、Stack 类的实际案例:括号匹配
1. 问题描述
编写一个程序,验证字符串中的括号是否匹配。例如,输入 "({[]})"
返回 true
,输入 "({[)]}"
返回 false
。
2. 实现思路
使用栈来记录每个左括号的位置。当遇到右括号时,检查栈顶的左括号是否匹配。
public static boolean isValid(String s) {
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '{' || c == '[') {
stack.push(c);
} else {
if (stack.empty()) return false;
char top = stack.pop();
if ((c == ')' && top != '(') ||
(c == '}' && top != '{') ||
(c == ']' && top != '[')) {
return false;
}
}
}
return stack.empty();
}
3. 代码解析
- 入栈逻辑:左括号入栈,记录顺序。
- 出栈逻辑:右括号时弹出栈顶元素并验证匹配性。
- 最终检查:若栈非空,说明存在未匹配的左括号。
五、Stack 类的进阶用法与优化
1. 泛型与类型安全
Java 5 引入泛型后,Stack
支持泛型参数,避免类型转换错误。例如:
Stack<String> stringStack = new Stack<>();
stringStack.push("Hello"); // 正确
stringStack.push(123); // 编译报错,类型不匹配
2. 替代方案:Deque 接口
Java 6 后推荐使用 Deque
接口(如 ArrayDeque
)实现栈功能,因其性能更优且线程安全选项更灵活。例如:
Deque<Integer> dequeStack = new ArrayDeque<>();
dequeStack.push(100); // 使用Deque的push方法
int value = dequeStack.pop();
3. 性能优化建议
- 预分配容量:通过构造函数指定初始容量,减少扩容开销。例如:
Stack<Integer> stack = new Stack<>(100); // 初始容量为100
- 避免频繁同步:在单线程场景中,优先使用
Deque
替代Stack
。
六、常见问题与解决方案
1. 为什么 Stack 类不推荐在多线程中使用?
Stack
的线程安全机制基于 Vector
的 synchronized
方法,但其同步粒度过粗,可能导致性能瓶颈。多线程场景建议使用 Deque
的 ArrayDeque
并配合同步包装器:
Deque<String> threadSafeDeque = new ArrayDeque<>();
threadSafeDeque = Collections.synchronizedDeque(threadSafeDeque);
2. 如何避免 EmptyStackException
?
在调用 pop()
或 peek()
前,始终检查 empty()
方法:
if (!stack.empty()) {
stack.pop();
}
结论
Java Stack 类
是实现后进先出(LIFO)数据结构的便捷工具,其核心方法简洁直观,适用于括号匹配、表达式求值等典型场景。然而,开发者需注意其线程安全性和性能限制。随着 Java 版本的演进,Deque
接口的实现类(如 ArrayDeque
)逐渐成为更优的选择。
通过本文的讲解,读者应能掌握 Stack
类的基本用法、潜在问题及替代方案,从而在实际开发中灵活运用这一经典数据结构。