简体   繁体   中英

Mutable object field within an Immutable class

I was assigned to create an immutable queue class in java and in that process what I did is actually had a private final arraylist, a startIndex, a lastIndex as fields. Eachtime I perform enqueue what I do is add the new value on top of the arraylist and return a new instance of class with a changed start and last index value. The dequeue operation also performs in a similar fashion. This way, after each enqueue/dequeue operation the new instance has its own start index, last index value of the array list while the previous instance holds its old value for those fields.

My question is that can I call this class immutable? Thank You.

No, the class is not immutable.

Basically, threads and the Java Memory Model are part of the language specification, so those parts of the language have to be considered whenever you ask about the behavior of a class. Since ArrayList is not thread safe, and you mutate it, you cannot rely on that ArrayList to exhibit expected behavior, such as returning the first or last element in its array, in the presence of multiple threads.

The Java Language Specification says:

While most of the discussion in the preceding chapters is concerned only with the behavior of code as executed a single statement or expression at a time, that is, by a single thread, the Java Virtual Machine can support many threads of execution at once. These threads independently execute code that operates on values and objects residing in a shared main memory...

Threads are represented by the Thread class. The only way for a user to create a thread is to create an object of this class; each thread is associated with such an object. A thread will start when the start() method is invoked on the corresponding Thread object.

The behavior of threads, particularly when not correctly synchronized, can be confusing and counterintuitive.

This is the money shot here, in Section 17.4.5:

More specifically, if two actions share a happens-before relationship, they do not necessarily have to appear to have happened in that order to any code with which they do not share a happens-before relationship. Writes in one thread that are in a data race with reads in another thread may, for example, appear to occur out of order to those reads.

Since your ArrayList is not thread safe (no happens-before relationship) writes can occur in any order -- you may see the initial values when the internal array was initialized to nulls, some previous write, or the latest write. One cannot tell.

So it is counter intuitive, because you think that if you have an index to the last element of an ArrayList , then that ArrayList will return the value of its last element, but the specification says "no."

Read that specification that I linked to, and get a copy of Java Concurrency in Practice by Brian Goetz. That book is basically the bible of all things threads and Java.

For immutability, the internal state of the queue's fields must never change as a result of enqueueing and dequeueing elements. One way to do this would be to create an entirely new instance of the queue and backing ArrayList each time. This will perform quite poorly in comparison to usual queue implementations, as both enqueue and dequeue operations will take O(n) time.

I am not sure what use this is, as usually queue's are assumed to change state - but perhaps you are looking for something like this:

public class ImmutableQueue<T> {

    private final List<T> elements;

    public ImmutableQueue() {
        elements = new ArrayList<T>();
    }

    private ImmutableQueue(List<T> elements) {
        this.elements = elements;
    }

    public ImmutableQueue<T> enqueue(T element) {
        List<T> copy = new ArrayList<T>(elements);
        copy.add(element);
        return new ImmutableQueue<T>(copy);
    }

    public DequeueResult dequeue() {
        if (elements.size() == 0) {
            throw new NoSuchElementException();
        }
        List<T> copy = new ArrayList<T>(elements);
        T head = copy.remove(0);
        return new DequeueResult(head, new ImmutableQueue<T>(copy));
    }

    public class DequeueResult {

        public final T element;
        public final ImmutableQueue<T> queue;

        public DequeueResult(T element, ImmutableQueue<T> queue) {
            this.element = element;
            this.queue = queue;
        }

    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM