可变与不可变

时间:2015-09-24 16:02:12

标签: stack push immutability mutable pop

应该考虑使可变类不可变?例如:我们仍然可以在不可变的堆栈类中使用push和pop方法吗?或者我们应该简单地删除任何改变实例化对象状态的方法?

3 个答案:

答案 0 :(得分:2)

底线是:您最好删除从类中修改实例化对象状态的方法。但是如果你想保留它,那么它应该创建一个与原始状态具有不同状态的新对象并返回新对象。

这是一个更具体的答案:

不应该有任何方法改变不可变类中的对象状态。

有许多void方法可以在可变类中更改此对象的状态。所以我们应该改变它们的签名,以一种方式返回一个新的对象而不是改变"这个"对象的状态。

还有很多非空方法可以改变"这个"对象并返回他们在"这"中更改的值。这些方法的签名也应该以它们返回一个新对象的方式改变,而不是改变"这个"的状态。谈论列表,通常还需要另一种方法(比如" peek")来获得一定的价值。检查下面的样本,得到我的意思:

查看这些"推送"和" pop"可变堆栈类的方法:

public class Stack <E> {
…
public void push (E e) {
     ensureCapacity();  // This method checks for capacity
     elements[size++] = e;
   }

此方法在堆栈顶部添加一个新元素,并更改&#34;这个&#34;的状态。以这种方式反对。

public E pop () {
     if (size == 0) throw new IllegalStateException("Stack.pop");
     E result = elements[--size];
     elements[size] = null;
     return result;
   }
…
}

此方法删除堆栈顶部的元素并将其返回并更改&#34;的状态&#34;通过删除元素来对象。

现在,假设我们需要更改这些方法以使此堆栈不可变。让我们来处理&#34; push&#34;方法第一:

&#34;推&#34;是一种无效的方法,可以改变&#34;这个&#34; object通过向其添加新元素。为了使堆栈不可变,我们将创建一个类似于&#34的新堆栈;这个&#34;并将新元素添加到此新堆栈并将其返回:

public class ImmStack <E> {
...
    /**
     * this method pushes the item to a new object and keeps the original object unchanged 
     * @param e The item to be pushed
     * @return A new list
     * @PRE An original list object is required as well as an item to be pushed
     * @POST A new list would be returned
     */
    @SuppressWarnings({ "unchecked", "rawtypes" }) // All items in this.elements[] are of type E
    public ImmStack<E> push (E e) {

         ImmStack<E> stc = new ImmStack(getNewElements());       
         stc.elements=ensureCapacity(stc.elements);
         stc.elements[size] = e;
         stc.size = size +1;         
         return stc;
       }

&#34;弹出&#34;方法改变了&#34;这个&#34;通过删除元素来对象。为了使类不可变,我们将创建一个类似于&#34的新堆栈;这个&#34;并从这个新堆栈中删除该元素并将其返回:

   /**
     * This pop method returns a new stack without the element at the top of the original list 
     * @return The new stack
     * @POST The new stack would be returned
     */
    @SuppressWarnings({ "unchecked", "rawtypes" }) // All items in this.elements[] are of type E
    public ImmStack<E> pop () {

        if (size == 0) throw new IllegalStateException("Stack.pop");

        ImmStack<E> stc = new ImmStack(getNewElements());        
         stc.elements=ensureCapacity(stc.elements);
         stc.elements[size-1] = null;
         stc.size=size-1;  
         return stc;
       }

旧的&#34; pop&#34;方法是返回顶部的元素。我们还需要一个新方法,它返回顶部的元素来覆盖这个特性:

   /**
    * Returns item at front of queue without removing.
    * @return item at front
    * @throws java.util.NoSuchElementException if empty
    */
   public E top()
   {
    if (this.isEmpty())
        {
        throw new NoSuchElementException("Queue underflow");
        }
       return elements[size-1];
   }

这只是一个例子。您可能有更多方法可以在类中进行更改以使其不可变。

答案 1 :(得分:1)

如果您的堆栈是不可变的,那么根据定义它无法更改。无法完成push()pop()方法。

如果无法成功完成某个方法,则可以抛出异常。当一个方法永远不会成功完成时,抛出的标准异常是UnsupportedOperationException

例如:

public E[] push (E e) {
    throw new UnsupportedOperationException();
} 

修改

您在注释中注意到您的push()方法只是使用新元素返回堆栈的深层副本。看起来您将不可变堆栈表示为类的实例,并将推送堆栈表示为数组。

您可以使用newElements获取newElements.length引用的两个数组之一的大小。所以你可以写这样的代码:

public E[] push (E e) {
   E[] newElements=getNewElements();
   int oldLength = newElements.length;
   newElements=ensureCapacity(newElements);
   int lastIndexInNewArray = oldLength;
   newElements[ lastIndexInNewArray ] = e;
   return newElements;
}

答案 2 :(得分:0)

下面是C#中不可变堆栈的实现。

推送和弹出会给你一个全新的堆栈,而Peek让你可以看到堆栈的顶部而不会弹出它。
请注意,不必复制整个堆栈。

这就是在任何重要案例中实现不可变结构的方式。在某些情况下,非平凡的不可变结构非常有用。海报说不能做到这一点很多都是错误的。

原始代码和更多信息可以在这里找到:
https://blogs.msdn.microsoft.com/ericlippert/2007/12/04/immutability-in-c-part-two-a-simple-immutable-stack/

public interface IStack<T> : IEnumerable<T>
{
    IStack<T> Push(T value);
    IStack<T> Pop();
    T Peek();
    bool IsEmpty { get; }
}

public sealed class Stack<T> : IStack<T>
{
    private sealed class EmptyStack : IStack<T>
    {
        public bool IsEmpty { get { return true; } }
        public T Peek() { throw new Exception("Empty stack"); }
        public IStack<T> Push(T value) { return new Stack<T>(value, this); }
        public IStack<T> Pop() { throw new Exception("Empty stack"); }
        public IEnumerator<T> GetEnumerator() { yield break; }
        IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); }
    }
    private static readonly EmptyStack empty = new EmptyStack();
    public static IStack<T> Empty { get { return empty; } }
    private readonly T head;
    private readonly IStack<T> tail;
    private Stack(T head, IStack<T> tail)
    {
        this.head = head;
        this.tail = tail;
    }
    public bool IsEmpty { get { return false; } }
    public T Peek() { return head; }
    public IStack<T> Pop() { return tail; }
    public IStack<T> Push(T value) { return new Stack<T>(value, this); }
    public IEnumerator<T> GetEnumerator()
    {
        for(IStack<T> stack = this; !stack.IsEmpty ; stack = stack.Pop())
            yield return stack.Peek();
    }
    IEnumerator IEnumerable.GetEnumerator() {return this.GetEnumerator();}
}