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 的线程安全机制基于 Vectorsynchronized 方法,但其同步粒度过粗,可能导致性能瓶颈。多线程场景建议使用 DequeArrayDeque 并配合同步包装器:

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 类的基本用法、潜在问题及替代方案,从而在实际开发中灵活运用这一经典数据结构。

最新发布