views:

1039

answers:

4

I've implemented a class in Java which, internally, stores a List. I want the class to be immutable. However, I need to perform operations on the internal data which don't make sense in the context of the class. Hence, I have another class which defines a set of algorithms. Here is a simplified example:

Wrapper.java

import java.util.List;
import java.util.Iterator;

public class Wrapper implements Iterable<Double>
{
    private final List<Double> list;

    public Wrapper(List<Double> list)
    {
     this.list = list;
    }

    public Iterator<Double> iterator()
    {
     return getList().iterator();
    }

    public List<Double> data() { return getList(); }
}

Algorithm.java

import java.util.Iterator;
import java.util.Collection;

public class Algorithm
{
    public static double sum(Collection<Double> collection)
    {
     double sum = 0.0;
     Iterator<Double> iterator = collection.iterator();

     // Throws NoSuchElementException if the Collection contains no elements
     do
     {
      sum += iterator.next();
     }
     while(iterator.hasNext());

     return sum;
    }
}

Now, my question is, is there a reliable way to prevent someone from modifying my internal data despite the fact that my class is meant to be immutable? While I have provided a data() method for read-only purposes, there is nothing to stop someone from modifying the data via methods such as clear() and remove(). Now, I realize that I could provide access to my data exclusively via iterators. However, I have been told that it is typical to pass a Collection. Secondly, if I had an algorithm that required more than one pass over the data, I would have to provide multiple iterators, which seems like a slippery slope.

Okay, hopefully there is a simple solution that will resolve my concerns. I am just getting back into Java, and never considered these things before dealing with const in C++. Thanks in advance!

Oh! There's one more thing I just thought of. I can't practically return a copy of the internal List. The List will typically contain hundreds of thousands of elements.

+15  A: 

You can use Collections.unmodifiableList and modify the data method.

public List<Double> data() { return Collections.unmodifiableList(getList()); }

From the javadoc:

Returns an unmodifiable view of the specified list. This method allows modules to provide users with "read-only" access to internal lists. Query operations on the returned list "read through" to the specified list, and attempts to modify the returned list, whether direct or via its iterator, result in an UnsupportedOperationException.

Alex B
Perfect! I didn't see this. Thank you.
You still have to take care of the way the Wrapper object is created: since the List is passed to the constructor, there might be references to it somewhere else in the code.If you want Wrapper to be truly immutable, you'll have to copy the content of the list.
Steph
+2  A: 

Java does not have a syntactic concept of immutable class. It is up to you, as the programmer, to give access to operations, but you always have to assume someone would abuse it.

A truly immutable object doesn't offer a way for people to change the state or to have access to state variables that can be used to change state. Your class is not immutable as it is now.

One way to make it immutable is to return a copy of the internal collection, in which case you should document it very well and warn people of using it in high-performance code.

Another option is to use a wrapper collection that would throw exceptions at runtime if someone tried to change the value (not recommended, but possible, see apache-collections for an example). I think the standard library has one too (look under the Collections class).

A third option, if some clients change data while others don't, is to provide different interfaces for your class. Let's say you have an IMyX and IMyImmutableX. The latter just defines the "safe" operations, while the former extends it and adds the unsafe ones.

Here are some tips on making immutable classes. http://java.sun.com/docs/books/tutorial/essential/concurrency/imstrat.html

Uri
I really like Alex B's suggestion. As I mentioned in my post, I cannot return a copy of the *List*. In your answer, you mention that you do not recommend returning an unmodifiable view of the list, as Alex suggested. Can you please elaborate?
@Scott: There is a general approach in programming that says "Don't surprise your users", or "prefer compile-time errors to runtime errors". It is generally preferable to have the compiler find a problem rather than having it crash your user's program in production. With an unmodifiable list, you return a valid list, the list can then be passed around until much much later it can suddenly "explode" when somebody tries to modify it.
Uri
@Scott: If you do need to return a list and cannot copy it, then the cost you will have to take is this runtime exception thing. But maybe consider naming your function "getUnmodifiableList" or something like that. My research showed that most people would never actually read the docs of functions that seem intuitive, like data()
Uri
+3  A: 

Can you use Collections.unmodifiableList?

According to the documentation, it will return a unmodifiable (immutable) view of the List. This will prevent the use of methods such as remove and add by throwing an UnsupportedOperationException.

However, I don't see that it won't prevent the modification of the actual elements in the list itself, so I'm not quite sure if this is immutable enough. At least the list itself cannot be modified.

Here's an example where the internal values of the List returned by unmodifiableList can still be altered:

class MyValue {
    public int value;

    public MyValue(int i) { value = i; }

    public String toString() {
        return Integer.toString(value);
    }
}

List<MyValue> l = new ArrayList<MyValue>();
l.add(new MyValue(10));
l.add(new MyValue(42));
System.out.println(l);

List<MyValue> ul = Collections.unmodifiableList(l);
ul.get(0).value = 33;
System.out.println(l);

Output:

[10, 42]
[33, 42]

What this basically shows is that if the data contained in the List is mutable in the first place, the contents of the list can be changed, even if the list itself is immutable.

coobird
My List will only ever contain Immutable Objects which extend the Number class (e.g. Integer, Double, etc.).
Ah, then that's one less thing to worry about :)
coobird
+2  A: 

There are a number of things to make your class correctly immutable. I believe this is discussed in Effective Java.

As mentioned in a number of other answers, to stop modification to list through the returned iterator, Collections.unmodifiableList offers a read-only interface. If this was a mutable class, you might want to copy the data so that the returned list does not change even if this object does.

The list that is passed to the constructor may later be modified, so that needs to be copied.

The class is subclassable, so methods can be overridden. So make the class final. Better provide a static creation method in place of the constructor.

public final class Wrapper implements Iterable<Double> {
    private final List<Double> list;

    private Wrapper(List<Double> list) {
        this.list = Collections.unmodifiableList(new ArrayList<Double>(list));
    }

    public static Wrapper of(List<Double> list) {
         return new Wrapper(list);
    }

    public Iterator<Double> iterator() {
        return list.iterator();
    }

    public List<Double> data() {
        return list;
    }
}

Also avoiding tabs and putting braces in the correct position for Java would be helpful.

Tom Hawtin - tackline
+1 for recommending Effective Java. :)
cwash