Java中栈Stack的bug(继承) 以及自己实现一个栈 支持范型 动态扩展

2023-11-06

问题

import java.util.Stack;

public class Main {
    public static void main(String[] args) {
        Stack s = new Stack();
        s.push(1);
        s.push(2);
        s.add(0, 666);
        System.out.println(s.get(0));
    }
}

看看上面这段代码, 运行起来是没有报错的, 运行结果就是 666
这就很离谱, Stack居然支持随机插入???

这其实是原因也很简单, Vector是动态数组, Java中的Stack继承自Vector, 也就把Vector中所有公有方法都继承过来了, 包括Vector中的 add()

public class Stack<E> extends Vector<E> 
public class Vector<E> extends AbstractList<E>

害 甲骨文你可长点心吧, 别整天打官司

这个问题告诉我们, 继承不能乱用, 能不用就不用, 因为继承打破了类的封装性, 父类的代码修改时, 可能值类也得修改, 子类依赖于父类

那不用继承用啥?
答案是组合!

继承和组合有啥区别?
继承是is-a的关系, 组合是has-a的关系

比如Tiger is a Animal, Animal就是Tiger的父类, 那Stack is a 动态数组? 我们可以考虑一下下面这个原则

如果设计成继承关系的话,我们是否有可能把子类进行向上的父类转型 如果可能,则应该设计成继承关系,否则应该是组合关系

上面的意思就是说能否把栈当成动态数组来使用, 答案是不可以, 栈不能像动态数组一样随机插入, 老虎可以像动物一样吃喝拉撒睡, 所以Stack不应该用继承, 应该用组合, 像这样

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

	// ...
}

有的人可能会问甲骨文会不知道这个吗? 为什么不改? 因为要向下兼容, 这里就不展开了

解决一: 封装Stack

后来Java官方推荐说使用Deque, 但是Deque本质是一个双向队列, 问题还是没有解决

下面是别人把Deque做一层封装实现Stack的代码
Stack.java

public interface Stack<T> {
    void push(T object);
    T pop();
}

DequeStack.java

import java.util.ArrayDeque;
import java.util.Deque;

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();
    }
}

解决二: 自己实现

可以自己顺便实现一个动态数组, 也可以封装ArrayList, 这里我选择自己实现

Array.java

public class Array<E> {
    private E[] data;
    private int size;  // 指向第一个没有元素的位置, 同时表示数组大小

    /**
     * 构造函数
     * @param capacity 数组的容量
     */
    public Array(int capacity){
        data = (E[])new Object[capacity];
        size = 0;
    }

    /**
     * 无参构造函数, 默认数组容量capacity=10
     */
    public Array(){
        this(10);
    }

    public Array(E[] arr){
        data = (E[])new Object[arr.length];
        for(int i=0; i<arr.length; i++){
            data[i] = arr[i];
        }
        size = arr.length-1;
    }

    /**
     * 获取数组的元素个数
     * @return 数组的元素个数
     */
    public int getSize(){
        return size;
    }

    /**
     * @return 数组的容量
     */
    public int getCapacity(){
        return data.length;
    }

    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 添加元素
     * @param index 坐标, 从0开始
     * @param e 元素值
     */
    public void add(int index, E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Add failed. Require index >=0 and index <= size. ");
        }

        if(size == data.length){
            resize(2 * data.length);
        }

        // index后面的都往后挪
        int i=size-1;
        while (i>=index) {
            data[i+1] = data[i];
            i--;
        }
        data[index] = e;
        size++;
    }

    /**
     * 添加一个元素到数组最后
     * 时间复杂度(均摊复杂度) O(1), 考虑到不是每次都会触发resize(), 即最坏的情况(O(n))不是每次都发生, 平均下来每次addLast要进行两次操作
     * @param e
     */
    public void addLast(E e){
        add(size, e);
    }

    /**
     * 添加一个元素到数组最前
     * @param e
     */
    public void addFirst(E e){
        add(0, e);
    }

    public E get(int index){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Get failed. Index is illegal. ");
        }
        return data[index];
    }

    public E getLast(){
        return get(size - 1);
    }

    public E getFirst(){
        return get(0);
    }

    public void set(int index, E e){
        if(index < 0 || index > size){
            throw new IllegalArgumentException("Set failed. Index is illegal. ");
        }
        data[index] = e;
    }

    public boolean contains(E e){
        for(int i=0; i<size; i++){
            if(data[i].equals(e)){  // 引用比较, ==是值比较
                return true;
            }
        }
        return false;
    }

    public int find(E e){
        for(int i=0; i<size; i++){
            if(data[i].equals(e)){
                return i;
            }
        }
        return -1;
    }

    /**
     * 删除元素
     * @param index 元素坐标, 从0开始
     * @return 被删元素的值
     */
    public E remove(int index){
        if(index < 0 || index >= size){
            throw new IllegalArgumentException("Remove failed. Require index >=0 and index < size. ");
        }

        E ret = data[index];

        // index后面的都往前挪
        int i=index+1;
        while (i<size) {
            data[i-1] = data[i];
            i++;
        }
        size--;  // 此时size依然存着一个对象的引用, 所以不会被java的自动回收机制回收
        data[size] = null;  // 以方便回收, loitering object != memory leak

        // 不要太Eager, Lazy一些能避免复杂度震荡
        if(size == data.length/4 && data.length/2 != 0){  // 小心size=0, length=1的情况
            resize(data.length/2);
        }
        return ret;
    }

    public E removeFirst(){
        return remove(0);
    }

    public E removeLast(){
        return remove(size-1);
    }

    /**
     * 删除指定值的第一个元素
     * @param e 元素值
     * @return 删除成功返回true, 否则false
     */
    public boolean removeElement(E e){
        int index = find(e);
        if(index != -1){
            remove(index);
            return true;
        }
        return false;
    }

    /**
     * 删除指定值的所有元素
     * @param e 元素值
     */
    public void removeAllElement(E e){
        while(removeElement(e));
    }

    /**
     * 动态改变容量
     * @param newCapacity 设定容量的大小
     */
    private void resize(int newCapacity) {
        E[] newData = (E[])new Object[newCapacity];
        int i=0;
        while (i<size) {
            newData[i] = data[i];
            i++;
        }
        data = newData;
    }

    /**
     * 交换索引为i和j的两个元素
     * @param i
     * @param j
     */
    public void swap(int i, int j){
        if(i<0 || i>=size || j<0 || j>=size)
            throw new IllegalArgumentException("Index is illegal");

        E tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
    }

    @Override  // 继承重写标志
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array: size = %d, capacity = %d\n", size, data.length));
        res.append('[');
        for(int i=0; i<size; i++){
            res.append(data[i]);
            if(i != size-1){
                res.append(", ");
            }
        }
        res.append(']');
        return res.toString();
    }
}

ArrayStack.java

public class ArrayStack<E> {
    Array<E> array;

    public ArrayStack(int capacity){
        array = new Array<>(capacity);
    }

    public ArrayStack(){
        array = new Array<>();
    }

    @Override
    public int getSize() {
        return array.getSize();
    }

    @Override
    public boolean isEmpty() {
        return array.isEmpty();
    }

    public int getCapacity(){
        return array.getCapacity();
    }

    @Override
    public void push(E e) {
        array.addLast(e);
    }

    /**
     * 弹出栈顶元素
     * @return 元素值
     */
    @Override
    public E pop() {
        return array.removeLast();
    }

    @Override
    public E peek() {
        return array.getLast();
    }

    @Override
    public String toString(){
        StringBuilder res = new StringBuilder();
        res.append("Stack: ");
        res.append('[');
        for(int i=0; i<array.getSize(); i++){
            res.append(array.get(i));
            if(i != array.getSize()-1){
                res.append(", ");
            }
        }
        res.append("] top");
        return res.toString();
    }
}

为什么不用链表, 用数组呢? 因为对于链表来说, 每新添一个元素, 都要new一个Node类的对象, 这就意味着一次内存操作, 学过操作系统都知道, 内存操作一般都是系统运行的瓶颈, 所以面对大规模数据的时候,不应该使用链表, 所以就不放代码了溜了

Reference

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java中栈Stack的bug(继承) 以及自己实现一个栈 支持范型 动态扩展 的相关文章

随机推荐

  • 登录注册,文件增删查改实现

    登录注册文件增删查改实现 需求 实现登录功能 注册功能 登录后文件可以进行增加删除修改查看等基本功能的操作 知识点 mybatis Tomcat servlet asion json req resp session 前提准备 pom xm
  • 对话量子链创始人帅初:区块链发展目标是构建协同进化的生命体

    有人说 区块链没有春节 只有春天 2月17日 大年初二 Qtum量子链创始人帅初在社区分享了自己关于区块链的25个看法 涵盖了公有链技术演进 区块链项目估值模型 区块链领域投资机会 区块链技术未来畅想等方方面面 引发了热议 ETH最大的风险
  • cocos 2.4.10升级到3.7

    Cocos Creator 3D v1 2 0 新版本中的cc找不到的解决办法 NZD Target的博客 CSDN博客 https www cnblogs com creator star p 17041314 html
  • 农夫和奶牛-二分(未完成没搞懂题目)

    农夫 John 建造了一座很长的畜栏 它包括N 2 lt N lt 100 000 个隔间 这些小隔间依次编号为x1 xN 0 lt xi lt 1 000 000 000 但是 John的C 2 lt C lt N 头牛们并不喜欢这种布局
  • c++11 std::enable_if在模板偏特化的妙用

    1 模板自动推导功能 先看个例子 在调用TestTemplate函数时 我们可以在函数后面加上 lt 类型 gt 无歧义地指定调用的版本 结果如下 由于模板参数在函数参数中的位置是固定的 编译器其实可以推导出参数的类型 这样程序员们就可以不
  • 无线网络几种攻击方式

    Evil Twin Attack 双面恶魔攻击 攻击者使用相同的SSID创建一个欺诈性接入点 因为与受害者常用SSID名称一样 并且具有更强的型号 因此可以轻易欺骗受害者与之连接 建立连接后 攻击者可以替换网页 比如亚马逊付费界面替换成攻击
  • 字符串转换成数字的方法【C#】

    在C 中 经常需要将字符串转换成数字 简单总结三种方法 一 Convert 将一个基本数据类型转换成另一个基本数据类型 比如 将用户输入的数学成绩进行转换 int math Convert ToInt32 Console ReadLine
  • Nginx+Tomcat负载均衡、动静分离

    一 Tomcat多实例部署 Tomcat的多实例部署简单的讲就是基于端口的虚拟主机设置 1 1 安装jdk 1 安装jdk 某rpm包尚未安装 我们可以通过该命令查询其说明信息 安装以后会生成的文件 rpm qpl jdk 8u201 li
  • oracle查询某一个字段的数量总和

    select count from select count from 表名称 group by 多种数据量 表名 举个栗子 比如说我有一个数据类型的字段 里面有很多种的数据类型 而且每个数据类型都有近些年的数据 就是有很多重复的数据类型的
  • 【踩坑专栏】0%classes,0% lines covered

    这东西一般都是不小心点到debug按钮右边的coverage按钮出现的 解决办法 Ctrl Alt F6 取消勾选你的应用 点击最左侧的show detected 或直接点击下方中间的no coverage 参考文章 1 IDEA 项目结构
  • python连接数据库

    参考python核心编程 编写一个用户洗牌的脚本 根据用户输入 选择连接sqlserver或者MySQL 创建数据库 表 随机生成数据 并实现增删改查 其中 为了兼容python2和python3 统一了打印函数 录入函数 动态导包等 一些
  • mysql怎么让表中某一列字段按某字符分割一行变成多行

    注意暂时看不懂的请看下列的解析方法 代码下面有具体解释 SELECT a XH substring index substring index a QZYSBM b help topic id 1 1 AS splitName FROM S
  • 【C++】deque容器

    0 前言 1 deque构造函数 include
  • 计网实验A3:简单的web服务器

    文章目录 计网实验A3 简单的web服务器 实验介绍 相关背景介绍 Socket编程接口 HTTP传输协议 实验功能要求 总体设计 详细设计 数据结构设计 函数分析 调试设计 运行结果 实验总结 困难与解决 心得与思考 计网实验A3 简单的
  • Andrew Ng机器学习算法入门((六):多变量线性回归方程求解

    多变量线性回归 之前讨论的都是单变量的情况 例如房价与房屋面积之前的关系 但是实际上 房价除了房屋面积之外 还要房间数 楼层等因素相关 那么此时就变成了一个多变量线性回归的问题 在实际问题中 多变量的线性回归问题是更加常见的 下面这个例子就
  • Tomcat 相关配置参数说明,性能调优

    Tomcat 相关配置参数说明 1 server xml connect中相关参数说明
  • 爬虫简单爬取网页图片

    仅供学习 请遵守法律法规和robots协议 请在爬取时设置爬取延时 防止给网站造成不必要的麻烦和损失 也避免给自己送进去 爬取图片一般需要导入的库有 import requests import re 正则表达式 import os os用
  • 多线程提高spark streaming数据写入到数据库

    多线程提高spark streaming数据写入到数据库 需求 集群环境资源有限 需要跑多个spark streaming任务 每个任务必须占据1核 cpu利用率很低 需要对数据进行实时统计更新到数据库mysql给业务实时展示 数据聚合程度
  • java-打印项目相对路径的根目录

    IDEA里 System out println System getProperty user dir
  • Java中栈Stack的bug(继承) 以及自己实现一个栈 支持范型 动态扩展

    问题 解决一 封装Stack 解决二 自己实现 Array java ArrayStack java 问题 import java util Stack public class Main public static void main S