代码改变世界

为什么质疑Java中的Stack

2013-05-16 18:36  飘扬的红领巾  阅读(4573)  评论(2编辑  收藏  举报

问题由来

    曾经遇到过一条面试题,“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:      }