• 为什么质疑Java中的Stack


    问题由来

        曾经遇到过一条面试题,“Java中的Stack是通过Vector来实现的,这种设计被认为是不良的设计,说说你的看法?”

    解析Java中的Stack

        众所周知Stack(栈)是一种先进后出的数据结构。当中有两个重要的方法:push(进栈)和pop(出栈)。

    2010062716323140

    几乎所有语言在实现栈时,都会实现这两个方法,进栈和出栈。而栈这种数据结构在多数时候用来插入和删除元素(进栈则是在顶部插入元素,出栈则是从顶部删除元素),较少情况会用来查找元素。所以从实现方式上,大多是以链表方式实现而非数值方式实现(在插入删除方法上,链表效率优于数组效率)。

    反观Java中的Stack,查看源代码:

       1:  public class Stack<E> extends Vector<E> {
       2:      /**
       3:       * Creates an empty Stack.
       4:       */
       5:      public Stack() {
       6:      }
       7:   
       8:      /**
       9:       * Pushes an item onto the top of this stack. This has exactly
      10:       * the same effect as:
      11:       * <blockquote><pre>
      12:       * addElement(item)</pre></blockquote>
      13:       *
      14:       * @param   item   the item to be pushed onto this stack.
      15:       * @return  the <code>item</code> argument.
      16:       * @see     java.util.Vector#addElement
      17:       */
      18:      public E push(E item) {
      19:      addElement(item);
      20:   
      21:      return item;
      22:      }
      23:   
      24:      /**
      25:       * Removes the object at the top of this stack and returns that
      26:       * object as the value of this function.
      27:       *
      28:       * @return     The object at the top of this stack (the last item
      29:       *             of the <tt>Vector</tt> object).
      30:       * @exception  EmptyStackException  if this stack is empty.
      31:       */
      32:      public synchronized E pop() {
      33:      E    obj;
      34:      int    len = size();
      35:   
      36:      obj = peek();
      37:      removeElementAt(len - 1);
      38:   
      39:      return obj;
      40:      }
      41:   
      42:      /**
      43:       * Looks at the object at the top of this stack without removing it
      44:       * from the stack.
      45:       *
      46:       * @return     the object at the top of this stack (the last item
      47:       *             of the <tt>Vector</tt> object).
      48:       * @exception  EmptyStackException  if this stack is empty.
      49:       */
      50:      public synchronized E peek() {
      51:      int    len = size();
      52:   
      53:      if (len == 0)
      54:          throw new EmptyStackException();
      55:      return elementAt(len - 1);
      56:      }
      57:   
      58:      /**
      59:       * Tests if this stack is empty.
      60:       *
      61:       * @return  <code>true</code> if and only if this stack contains
      62:       *          no items; <code>false</code> otherwise.
      63:       */
      64:      public boolean empty() {
      65:      return size() == 0;
      66:      }
      67:   
      68:      /**
      69:       * Returns the 1-based position where an object is on this stack.
      70:       * If the object <tt>o</tt> occurs as an item in this stack, this
      71:       * method returns the distance from the top of the stack of the
      72:       * occurrence nearest the top of the stack; the topmost item on the
      73:       * stack is considered to be at distance <tt>1</tt>. The <tt>equals</tt>
      74:       * method is used to compare <tt>o</tt> to the
      75:       * items in this stack.
      76:       *
      77:       * @param   o   the desired object.
      78:       * @return  the 1-based position from the top of the stack where
      79:       *          the object is located; the return value <code>-1</code>
      80:       *          indicates that the object is not on the stack.
      81:       */
      82:      public synchronized int search(Object o) {
      83:      int i = lastIndexOf(o);
      84:   
      85:      if (i >= 0) {
      86:          return size() - i;
      87:      }
      88:      return -1;
      89:      }
      90:   
      91:      /** use serialVersionUID from JDK 1.0.2 for interoperability */
      92:      private static final long serialVersionUID = 1224463164541339165L;

    可以看到Stack继承Vector,而Vector是由数组实现的集合类,他包含了大量集合处理的方法。而Stack之所以继承Vector,是为了复用Vector中的方法,来实现进栈(push)、出栈(pop)等操作。

    这里就是质疑Stack的地方,既然只是为了实现栈,为什么不用链表来单独实现,只是为了复用简单的方法而迫使它继承Vector(Stack和Vector本来是毫无关系的)。这使得Stack在基于数组实现上效率受影响,另外因为继承Vector类,Stack可以复用Vector大量方法,这使得Stack在设计上不严谨,当我们看到Vector中的:

       1:  public void add(int index, E element) {
       2:          insertElementAt(element, index);
       3:      }

    可以在指定位置添加元素,这与Stack 的设计理念相冲突(栈只能在栈顶添加或删除元素)。

    所以Java中的Stack实现确实值得商榷。

    使用链表模拟Stack

    一个单纯的栈,其实可以只包含以下方法:

    boolean empty()
    测试堆栈是否为空。 
    T peek()
    查看堆栈顶部的对象,但不从堆栈中移除它。 
    T pop()
    移除堆栈顶部的对象,并作为此函数的值返回该对象。 
    void push(T item)
    把项压入堆栈顶部。

    我们使用链表来模拟一个栈:

       1:  public class Stack<T> {
       2:      private Node<T> top;
       3:   
       4:      /**
       5:       * 返回栈顶元素
       6:       * 
       7:       * @return
       8:       */
       9:      public T peek() {
      10:          return top.getData();
      11:      }
      12:   
      13:      /**
      14:       * 从栈中弹出项
      15:       * @return
      16:       */
      17:      public T pop() {
      18:          T data = null;
      19:          if (top != null) {
      20:              data = top.getData();
      21:              top = top.getNextNode();
      22:          }
      23:          return data;
      24:      }
      25:   
      26:      /**
      27:       * 向栈内压入项
      28:       * 
      29:       * @param data
      30:       */
      31:      public void push(T data) {
      32:          Node<T> node = new Node<T>();
      33:          node.setData(data);
      34:          if (top == null) {
      35:              top = node;
      36:          } else {
      37:              node.setNextNode(top);
      38:              top.setPreNode(node);
      39:              top = node;
      40:          }
      41:      }
      42:   
      43:      /**
      44:       * 是否是空栈
      45:       * 
      46:       * @return
      47:       */
      48:      public boolean isEmpty() {
      49:          return top == null;
      50:      }
      51:  }
       1:  public class Node<T> {
       2:      private T data;
       3:      // 前驱节点
       4:      private Node<T> preNode;
       5:      // 后继节点
       6:      private Node<T> nextNode;
       7:   
       8:      public T getData() {
       9:          return data;
      10:      }
      11:   
      12:      public void setData(T data) {
      13:          this.data = data;
      14:      }
      15:   
      16:      public Node<T> getPreNode() {
      17:          return preNode;
      18:      }
      19:   
      20:      public void setPreNode(Node<T> preNode) {
      21:          this.preNode = preNode;
      22:      }
      23:   
      24:      public Node<T> getNextNode() {
      25:          return nextNode;
      26:      }
      27:   
      28:      public void setNextNode(Node<T> nextNode) {
      29:          this.nextNode = nextNode;
      30:      }
      31:   
      32:  }

    编写一个main测试:

       1:  /**
       2:       * @param args
       3:       */
       4:      public static void main(String[] args) {
       5:          Stack<Integer> stack = new Stack<Integer>();
       6:          stack.push(1);
       7:          stack.push(2);
       8:          System.out.println(stack.peek());
       9:          stack.push(3);
      10:          stack.push(4);
      11:          System.out.println(stack.peek());
      12:          stack.push(5);
      13:          stack.push(6);
      14:          System.out.println(stack.pop());
      15:          System.out.println(stack.peek());
      16:      }
  • 相关阅读:
    快速上手Unity原生Json库
    Unity3D安卓出包报错
    Git快速入门
    [Modern OpenGL系列(四)]在OpenGL中使用Shader
    [Modern OpenGL系列(三)]用OpenGL绘制一个三角形
    [Modern OpenGL系列(二)]创建OpenGL窗口
    [Modern OpenGL系列(一)]十步搞定OpenGL开发环境
    [Unity游戏开发]向量在游戏开发中的应用(三)
    [Unity游戏开发]向量在游戏开发中的应用(二)
    【leetcode】-两数之和
  • 原文地址:https://www.cnblogs.com/leefreeman/p/3082400.html
Copyright © 2020-2023  润新知