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

问题

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值