首页 游戏 软件 资讯 排行榜 专题
首页
编程语言
Java栈结构实现指南使用Stack类完成LIFO操作

Java栈结构实现指南使用Stack类完成LIFO操作

热心网友
22
转载
2026-05-07

如何在 Java 中使用 Stack 类实现后进先出的栈结构

如何在 Ja va 中使用 Stack 类实现后进先出的栈结构

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈

开门见山,直接给出核心结论:在 Java 编程中,Stack 类已被视为“遗留类”,不推荐在新项目中使用。 其根本问题源于其设计——它直接继承了过时的 Vector 类。这不仅引入了不必要的同步性能损耗,更重要的是,它破坏了栈数据结构应有的封装性。试想,一个栈对象竟然可以通过 get(i) 方法随意访问中间任意位置的元素,这完全违背了栈(LIFO)的基本原则。

现代 Java 开发的最佳实践是使用 Deque 接口及其实现类(例如 ArrayDeque)来模拟栈操作。后者不仅性能更优越,其 API 设计也更加纯粹,更符合栈的抽象定义。

为什么不该用 Stack

深入剖析,Stack 本质上是一个“披着栈外衣”的 Vector。其所有公共方法都标记了 synchronized 关键字,这意味着即使在单线程环境下,也会产生额外的同步开销,造成性能浪费。它的 push()pop() 方法,底层实际调用的是 VectoraddElement()removeElementAt(size()-1),存在语义上的冗余。

最关键的是,它从 Vector 父类继承了大量与栈概念相悖的方法,例如 elementAt()setSize() 等。这些方法的存在,无异于允许开发者随意破坏栈的后进先出(LIFO)约束。因此,JDK 官方文档早已明确建议:应当使用 Deque 接口及其实现类来替代 Stack 其中,ArrayDeque 因其基于数组、支持动态扩容且非线程安全的特性,成为绝大多数场景下的首选替代方案。

ArrayDeque 替代 Stack 的正确写法

替换操作非常简便。原先声明栈的代码:

Stack stack = new Stack<>();

现在可以无缝更改为:

Deque stack = new ArrayDeque<>();

核心栈操作方法几乎可以一一对应,迁移成本极低:

  • 入栈stack.push("a") → 保持不变(ArrayDeque 同样实现了 push 方法)。
  • 出栈stack.pop() → 行为完全一致,返回并移除栈顶元素。
  • 查看栈顶stack.peek() → 查看但不移除,用法与 Stack 完全相同。
  • 判断空栈stack.isEmpty() → 直接使用,毫无差异。

这里有一个重要细节需要注意:ArrayDeque 不允许存储 null 元素,而旧的 Stack 类则允许。如果现有业务逻辑确实依赖在栈中存储 null,那么或许应该重新审视这个栈本身的设计是否合理。

遇到 EmptyStackException 怎么办

无论是使用旧的 Stack.pop() 还是新的 ArrayDeque.pop(),在空栈上执行出栈操作时,都会抛出 EmptyStackException。这并非程序错误,而是容器类定义的标准契约行为。

安全的做法始终是“先检查,后操作”:

if (!stack.isEmpty()) {
    String top = stack.pop();
}

切忌依赖 try-catch 块来控制正常的业务逻辑流。这不仅可能掩盖程序其他部分抛出的真实异常,还会带来不必要的性能损耗。一些遗留代码可能使用 stack.size() > 0 进行判断,对于 ArrayDeque 这没有问题。但需要注意的是,对于某些特定的 Deque 实现(例如并发场景下的 ConcurrentLinkedDeque),size() 方法的计算开销可能较大或结果不精确。因此,最稳妥、最通用的做法就是坚持使用 isEmpty() 方法。

如果必须兼容老 Stack 接口怎么办

在实际开发中,难免会遇到需要对接遗留系统,或者某些第三方库的 API 强制要求传入 Stack 类型参数的情况。此时,我们的目标是以最小的侵入性来解决问题。

一个优雅的解决方案是采用适配器模式,而不是直接继承 Stack 或对其进行简单包装:

public class DequeStack extends Stack {
    private final Deque delegate = new ArrayDeque<>();

    @Override
    public E push(E item) {
        delegate.push(item);
        return item;
    }

    @Override
    public synchronized E pop() {
        if (delegate.isEmpty()) throw new EmptyStackException();
        return delegate.pop();
    }
    // 其他方法同理,只转发到 delegate,屏蔽 Vector 特性
}

这个方案有几个关键优势:

  • 彻底重写:重写所有 Stack 的方法,确保内部逻辑全部委托给高效的 ArrayDeque 实例。
  • 保留签名:方法上保留 synchronized 关键字仅仅是为了满足 Stack 原有的方法签名约定,内部委托操作实际上已无需同步。
  • 严格封装:绝不对外暴露内部的 delegate 对象,也绝不调用父类(Vector)的任何方法,从而彻底屏蔽掉 Vector 带来的不良特性和性能问题。

归根结底,技术选型的难点往往不在于语法本身,而在于对设计原则的深刻理解。栈(Stack)不仅仅是一个“能后进先出”的容器,它更意味着接口清晰、行为可预测、未来扩展无隐患。如果选错了底层实现,等到调试时,发现 Stack.size() 明明返回 5,但调用 peek() 却抛出异常,那时就不得不去深入排查令人头疼的源码了。防患于未然,总是更明智的选择。

来源:https://www.php.cn/faq/2424604.html
免责声明: 游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。

相关攻略

Java异步任务重试实现指南 do-while循环结合CompletableFuture应用
编程语言
Java异步任务重试实现指南 do-while循环结合CompletableFuture应用

Java中不应将同步do-while循环与异步CompletableFuture结合使用,这会阻塞线程。正确做法是采用递归式CompletableFuture链模拟“先执行、再判断”语义,实现非阻塞异步重试。核心是定义递归方法,在失败且满足条件时延迟调用自身,并控制最大重试次数以避免栈溢出。该方法可扩展支持退避策略和上下文透传。

热心网友
05.07
Java栈结构实现指南使用Stack类完成LIFO操作
编程语言
Java栈结构实现指南使用Stack类完成LIFO操作

Java中的Stack类因继承Vector存在性能与设计缺陷,不推荐使用。应改用Deque接口及ArrayDeque实现栈操作,其API更纯粹且性能更优。迁移时需注意ArrayDeque不支持null元素,空栈操作应优先使用isEmpty检查。适配遗留系统可采用适配器模式封装ArrayDeque。

热心网友
05.07
Java线程池中ThreadLocal内存泄漏的预防与remove方法使用指南
编程语言
Java线程池中ThreadLocal内存泄漏的预防与remove方法使用指南

在线程池场景中,由于线程复用,ThreadLocal设置的值不会自动清除。其内部ThreadLocalMap的value为强引用,若不手动调用remove(),前序任务的大对象或资源引用将滞留内存,导致泄漏。推荐重写线程池的afterExecute方法进行统一清理,确保在任务结束后移除数据。自定义ThreadLocal应声明为privatestaticfin

热心网友
05.07
SpringCloudStream动态路由Key配置与RabbitMQ实战指南
编程语言
SpringCloudStream动态路由Key配置与RabbitMQ实战指南

在异步消息处理场景中,需根据消息类型将其路由至不同队列。通过SpringCloudStream整合RabbitMQ,生产者可利用消息头动态指定路由键,消费者通过配置绑定特定路由键及消费者组,实现消息的精准分发。该方法将路由逻辑配置化,避免硬编码,提升了系统的可维护性与灵活性。

热心网友
05.06
如何修复Perplexity生成的Java代码中Deprecation警告报错_提示模型使用最新SDK版本
AI
如何修复Perplexity生成的Java代码中Deprecation警告报错_提示模型使用最新SDK版本

一、使用@SuppressWarnings注解临时抑制警告 当重构条件尚不成熟,又需要快速让编译通过时,这个方法可以派上用场。它本质上是在告诉编译器:“我知道这里用了旧东西,先别报警,容我缓缓。” 但务必记住,这只是权宜之计,代码的兼容性风险依然存在。 具体操作很简单:在调用废弃API的类、方法甚至

热心网友
05.06

最新APP

宝宝过生日
宝宝过生日
应用辅助 04-07
台球世界
台球世界
体育竞技 04-07
解绳子
解绳子
休闲益智 04-07
骑兵冲突
骑兵冲突
棋牌策略 04-07
三国真龙传
三国真龙传
角色扮演 04-07

热门推荐

Java对象比对防空指针指南Objects.equals方法安全使用详解
编程语言
Java对象比对防空指针指南Objects.equals方法安全使用详解

在Java中直接调用a equals(b)进行对象比较时,若a为null会抛出NullPointerException。使用Objects equals(a,b)方法能自动处理参数为null的情况,其内部通过先检查引用是否为null再调用equals,从而安全地完成比较。该方法适用于实体字段判等等场景,但需注意其将两个null视为相等的设计是否符合具体业务逻

热心网友
05.07
Java子线程崩溃全局捕获与处理指南ThreadsetUncaughtExceptionHandler方法详解
编程语言
Java子线程崩溃全局捕获与处理指南ThreadsetUncaughtExceptionHandler方法详解

全局拦截子线程崩溃需设置默认处理器并结合自定义ThreadFactory为每个新线程注入统一处理器,前者作为兜底方案,但无法覆盖已有专属处理器的线程及Android主线程。Android中还需额外处理主线程及异步框架异常。捕获崩溃后应留存现场、异步上报并防止雪崩。

热心网友
05.07
CMS垃圾收集器详解初始标记并发标记重新标记与并发清除阶段分析
编程语言
CMS垃圾收集器详解初始标记并发标记重新标记与并发清除阶段分析

CMS垃圾收集器以低延迟为目标,其四个阶段中仅初始标记和重新标记需要暂停所有用户线程。初始标记快速标记直接关联对象,重新标记修正并发标记期间变动的引用,两者停顿时间极短。而并发标记和并发清除阶段则与用户线程并行执行,避免了长时间中断。

热心网友
05.07
Java只读缓冲区创建指南ByteBufferasReadOnlyBuffer方法详解与数据保护实践
编程语言
Java只读缓冲区创建指南ByteBufferasReadOnlyBuffer方法详解与数据保护实践

ByteBuffer asReadOnlyBuffer()方法创建原缓冲区的只读视图,共享底层数据且禁止写入,但无法阻止通过其他可写引用修改数据,因此不提供真正的数据隔离。它适用于需只读访问且避免拷贝的场景;若需完全隔离,则应进行深拷贝。

热心网友
05.07
Java单例模式初始化空指针异常ExceptionInInitializerError排查指南
编程语言
Java单例模式初始化空指针异常ExceptionInInitializerError排查指南

ExceptionInInitializerError常包裹单例模式静态初始化时发生的空指针异常。排查需通过getCause()找到根源,通常是静态字段赋值或静态代码块中的空值。应注意静态初始化顺序,避免循环依赖。对于复杂初始化,推荐使用懒汉式并在getInstance()方法内进行异常处理,以便直接定位问题。

热心网友
05.07