Java 中的 Stack 好纠结~

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 【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(); 
    } 
}
目录
相关文章
|
1月前
|
Java
栈的简单应用(利用Stack进行四则混合运算)(JAVA)
中缀表达式转后缀表达式图解, 代码实现过程, 完整代码, 利用后缀表达式求值, 完整代码
51 0
|
1月前
|
算法 Java
Java栈Stack的使用
Java栈Stack的使用
33 0
|
1月前
|
安全 Java 容器
Java Review - Queue和Stack 源码解读
Java Review - Queue和Stack 源码解读
41 0
|
4天前
|
运维 Java 程序员
新手进阶:用对if-else,让你的Java逻辑判断不再纠结!
【6月更文挑战第14天】本文介绍了如何有效使用Java中的if-else语句。从基础开始,解释了if-else用于根据条件执行不同代码路径的功能。接着,通过实践演示如何避免过度嵌套以提高代码可读性,利用逻辑运算符简化条件判断,以及在异常处理中运用if-else提升程序健壮性。通过这些最佳实践,旨在帮助开发者更好地掌握if-else,使其成为编程工具箱中的利器。
|
17天前
|
Java
Java栈(Stack)深度解析与实现
Java栈(Stack)深度解析与实现
17 1
|
1月前
|
存储 算法 安全
Java集合篇之逐渐被遗忘的Stack,手写一个栈你会吗?
Java集合篇之逐渐被遗忘的Stack,手写一个栈你会吗?
20 0
|
11月前
|
Prometheus 监控 Kubernetes
一文搞懂基于 Prometheus Stack 监控 Java 容器
Hello folks,我是 Luga,今天我们来分享一下如何基于 Prometheus Stack 可视化监控运行在 Kubernetes Cluster 上的 Spring Boot 微服务容器实例。这里,主要针对每一个 Java 容器实例的指标进行监控,具体涉及:CPU、内存、线程信息、日志信息、HTTP 请求以及 JVM 等。
346 0
|
11月前
|
存储 安全 架构师
Java开发篇 - 还在为计算2个日期间隔的天数纠结?是时候换掉java.util.Date
即然Date及Calendar在计算时间间隔或者其他场景下都比较麻烦,那么有没有更好的API使用呢?当然是有的,JDK1.8中,就更新了新的日期/时间处理工具类。具体的包在java.time目录下,有兴趣的小伙伴可以打开进行查看。
288 0
|
12月前
|
Java
java中栈Stack的基本方法
java中栈Stack的基本方法
|
Java API Maven
带你读《Elastic Stack 实战手册》之67:——3.5.19.3.Elasticsearch语言开发(Java)(上)
带你读《Elastic Stack 实战手册》之67:——3.5.19.3.Elasticsearch语言开发(Java)(上)