Java 中的 Stack 好纠结~

本文涉及的产品
Serverless 应用引擎 SAE,800核*时 1600GiB*时
性能测试 PTS,5000VUM额度
云原生网关 MSE Higress,422元/月
简介: 【6月更文挑战第10天】栈是每一个程序员都很熟悉的数据结构,英文叫做 Stack,在 Java 中,栈的实现类是 java.util.Stack。如果你了解 Java 中的 Stack 类,就会知道,这里有一个历史遗留问题。

「栈」是每一个程序员都很熟悉的数据结构,英文叫做 Stack,在 Java 中,栈的实现类是 java.util.Stack。如果你了解 Java 中的 Stack 类,就会知道,这里有一个历史遗留问题:

CleanShot Safari on 2024-06-14 at 09.55.12@2x.png

这 Java 的源码,在注释中,Java 官方并不推荐使用这个 Stack 类,而是更推荐下面的写法:

Deque<Integer> stack = new ArrayDeque<Integer>();

也就是用一个 Deque 来作为「栈」来使用。

先说一下 Stack 的问题,这个问题的起源是 Stack 的继承关系。

CleanShot Safari on 2024-06-14 at 09.55.36@2x.png

它直接继承于 Vector,Vector 是 Java 中的一个动态数组的实现,乍一想,栈也是一个动态的容器结构,这里并没有什么大问题,但其实并非如此。问题主要来自于 Stack 和 Vector 之间的关系是继承关系,因此,Stack 会继承 Vector 中的公开方法,作为一个动态数组,Vector 允许在数组的任意一个位置插入元素,因此 Stack 类也可以进行如下的操作:

Stack<Integer> stack = new Stack<>();

// 正常的栈操作
stack.push(1);
stack.push(2);
stack.pop();

// 这里就很奇怪~
stack.add(0, 3);

如上注释中所说,我们可以像操作一个数组或者列表一样,向其中添加元素,这显然是不对的,这并不是一个栈结构应该具备的特性。暴露了不该暴露的行为,既违背了面向对象设计的原则,也会成为漏洞的来源。

简而言之,Java 中的 Stack 类存在很大的问题。

在面向对象设计原则中,有一个很重要的原则叫作「组合优于继承」,但是这条原则常常被忽略。其实在 Stack 类的问题上,我们就可以通过组合的方式来解决这个问题:

public class Stack<E> {
   
   
    private Vector<E> elementVector = new Vector<E>();

    /*
    栈结构相关的操作
    */
}

让 Stack 包含一个 Vector 结构,而不是继承 Vector,然后只暴露于栈结构相关的操作,就可以解决这个问题。那 Java 为什么不这样解决呢?原因是 Java 一直严格遵守自己的向后兼容性承诺,导致这个 Stack 只能原封不动地放在这里,而后,Java 推荐使用 Deque 来作为 Stack 使用。

Deque 是一个双端队列,队列是一个先进先出的结构,而双端队列的意思就是,两端都可以进出元素。我们再来看看 Java 推荐的栈结构写法:

Deque<Integer> stack = new ArrayDeque<Integer>();

在 Java 中,Deque 是一个接口,这里使用了 ArrayDeque 作为实现类,从名字可以看出这是一个通过数组实现的双端队列。除此之外,在 Java 中,LinkedList 也实现了 Deque 接口。

通常情况下,我们按照官方推荐的写法去写,应该是没问题的,但是,双端队列是可以两端都进出元素的,而栈结构只能一端进出元素,这……又回到了 Stack 类的问题。为了解决一个遗留问题提出的替代方案存在与遗留问题同样的问题……

因此,如果我们对栈的结构要求严谨的话,只能自己实现一个了,我们依然可以使用 Deque 来保存元素,然后只暴露栈操作的相关接口就可以了:

public interface Stack<T> {
   
    
    void push(T object); 
    T pop(); 
}
public class DequeStack<T> implements Stack<T> {
   
    
    private final Deque<T> deque = new ArrayDeque<T>(); 
    @Override 
    public void push(T object) {
   
    
        deque.addFirst(object); 
    } 
    @Override 
    public T pop() {
   
    
        return deque.removeFirst(); 
    } 
}
目录
相关文章
|
2月前
|
Java
栈的简单应用(利用Stack进行四则混合运算)(JAVA)
中缀表达式转后缀表达式图解, 代码实现过程, 完整代码, 利用后缀表达式求值, 完整代码
56 0
|
2月前
|
算法 Java
Java栈Stack的使用
Java栈Stack的使用
34 0
|
2月前
|
安全 Java 容器
Java Review - Queue和Stack 源码解读
Java Review - Queue和Stack 源码解读
43 0
|
2天前
|
存储 安全 Java
Java集合篇之逐渐被遗忘的Stack,手写一个栈你会吗?
总之,虽然在日常开发中,`java.util.Stack`正逐渐被其他类如 `Deque`接口的实现所取代,但手写一个栈(无论是基于数组还是链表)都是一次很好的编程练习,它可以帮助开发者更加深入地理解栈这种数据结构的工作原理和各种操作。
5 0
|
1月前
|
Java
Java栈(Stack)深度解析与实现
Java栈(Stack)深度解析与实现
55 1
|
1月前
|
运维 Java 程序员
新手进阶:用对if-else,让你的Java逻辑判断不再纠结!
【6月更文挑战第14天】本文介绍了如何有效使用Java中的if-else语句。从基础开始,解释了if-else用于根据条件执行不同代码路径的功能。接着,通过实践演示如何避免过度嵌套以提高代码可读性,利用逻辑运算符简化条件判断,以及在异常处理中运用if-else提升程序健壮性。通过这些最佳实践,旨在帮助开发者更好地掌握if-else,使其成为编程工具箱中的利器。
|
2月前
|
存储 算法 安全
Java集合篇之逐渐被遗忘的Stack,手写一个栈你会吗?
Java集合篇之逐渐被遗忘的Stack,手写一个栈你会吗?
27 0
|
Prometheus 监控 Kubernetes
一文搞懂基于 Prometheus Stack 监控 Java 容器
Hello folks,我是 Luga,今天我们来分享一下如何基于 Prometheus Stack 可视化监控运行在 Kubernetes Cluster 上的 Spring Boot 微服务容器实例。这里,主要针对每一个 Java 容器实例的指标进行监控,具体涉及:CPU、内存、线程信息、日志信息、HTTP 请求以及 JVM 等。
375 0
|
存储 安全 架构师
Java开发篇 - 还在为计算2个日期间隔的天数纠结?是时候换掉java.util.Date
即然Date及Calendar在计算时间间隔或者其他场景下都比较麻烦,那么有没有更好的API使用呢?当然是有的,JDK1.8中,就更新了新的日期/时间处理工具类。具体的包在java.time目录下,有兴趣的小伙伴可以打开进行查看。
296 0