0
public class TestImmutableCollection {
    static class Helper {
        int val;

        public Helper(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            return String.valueOf(val);
        }
    }

    public static void main(String[] args) {
        List<Helper> origin = new ArrayList<>();
        origin.add(new Helper(10));
        origin.add(new Helper(11));
        origin.add(new Helper(13));
        ImmutableList<Helper> ul = ImmutableList.copyOf(origin);
        ul.get(0).val = 15;
        System.out.println(ul);
        System.out.println(origin);
    }
}

I was asked about immutability in a previous interview thus I search the Internet on Immutable Collections in Java. So I ran into this post Java Immutable Collections where quite a few people referenced that Guava has a better and safer implementation of the Immutable Collections. Before using guava I have already tested the above code using JDK's built-in UnmodifiableList which turned out the UnmodifiableList is just a wrapper of the original list so that the content of both list will be updated if I use get to access the inner element and then update the field of the element object.

As people stated in the previous post:

Unlike Collections.unmodifiableList(java.util.List<? extends T>), which is a view of a separate collection that can still change, an instance of ImmutableList contains its own private data and will never change.

Then I test the code with guava ImmutableList. But still it gave the same result. Both the content of the ImmutableList created by copyOf() and the original list has changed.enter image description here

I am quite confused why turns out like that. Am I understanding the scope of immutability here? The change of content of the elements in the Collection won't be judge as a change to the Collection here? But the doc of guava says so absolutely that it will never change.

If so, what is the difference between guava ImmutableList and JDK's UnmodifiableList in this case?

Hope someone can draw a light on that. Appreciate that.


Updated: Well, I know it is not a good design not to add any access constraint on fields of a class. But just try to image a realistic case where the Helper can be like an Account of a user, the filed can be the user's username, you will certainly provide a method to update this username field , right? Then in this case how can I just show the info of the accounts in the list without letting the caller to modify the content of the element in this list?

Boyu Zhang
  • 219
  • 2
  • 12
  • Generally please put code in text rather than pasting an image. If you don't have the text to copy and paste and it's short, consider re-typing it instead. – AlBlue Jun 24 '20 at 08:53
  • Thanks for the advice but here the image is just for showing the standard output which no one would need to copy or edit on it. – Boyu Zhang Jun 24 '20 at 08:57
  • 1
    In both cases, it’s about the structure of the collection, not the contents of its elements. – Sotirios Delimanolis Jun 24 '20 at 08:59
  • 1
    The point is that those using text readers (or use other assistive text reading features, like magnifying the font) won't be able to take any value of an opaque image, but if you have it as text then they will be able to. It's not just about letting people copy and paste the text. – AlBlue Jun 24 '20 at 09:00

2 Answers2

3

Neither UnmodifiableList nor ImmutableList guarantee that elements stored in those collections won't ever change. The collections themselves are immutable, not elemenets stored in them. Those collections would have to return copies of stored elements but they don't. You can't add/remove elements to those collections but you can still modify elements themselves.

A quote from Javadocs for ImmutableCollection helps here a lot:

Shallow immutability. Elements can never be added, removed or replaced in this collection. This is a stronger guarantee than that of Collections.unmodifiableCollection(java.util.Collection<? extends T>), whose contents change whenever the wrapped collection is modified.

Basicly UnmodifiableList is just a wrapper around some other collection. If you somehow get hold of that wrapped collection, you can modify it (adding or removing elements) and those changes will be reflected in the UnmodifiableList itself.

ImmutableList on the other hand copies references to all elements of the original collection and doesn't use it anymore. The newly created ImmutableList and the original collection are thus separate, you can modify the original one (adding or removing elements) and won't see any changes in the ImmutableList.

And an extra quote from the Javadocs for ImmutableCollection:

Warning: as with any collection, it is almost always a bad idea to modify an element (in a way that affects its Object.equals(java.lang.Object) behavior) while it is contained in a collection. Undefined behavior and bugs will result. It's generally best to avoid using mutable objects as elements at all, as many users may expect your "immutable" object to be deeply immutable.

Edit to answer the question you added:

If you want caller to be able to do anything with those objects but don't want those changes to affect your original data you can make a deep copy - make copy of every single element in the collection.

Othe approach will be to write a wrapper for that Account class, with restricted access - without any setters. With a simple composition you block all modifications.

Amongalen
  • 3,101
  • 14
  • 20
  • But the ImmutableList is not that separated from the original one in that, modification of element's inner content will reflects in both list. Why would it behaves like that? Are there any logical consideration on that? – Boyu Zhang Jun 24 '20 at 09:14
  • @BoyuZhang You're right, I didn't phrase it correctly. `ImmutableList` copies **references** to all elements of the original collection - changed it in my answer. – Amongalen Jun 24 '20 at 10:15
2

You are not modifying the list, but one of the elements inside that list!

If you do the following

public static void main(String[] args) {
    List<Helper> origin = new ArrayList<>();
    origin.add(new Helper(10));
    origin.add(new Helper(11));
    origin.add(new Helper(13));
    ImmutableList<Helper> ul = ImmutableList.copyOf(origin);
    ul.get(0).val = 15;
    origin.add(new Helper(42)); // <-- Added another element here!
    System.out.println(ul);
    System.out.println(origin);
}

you will get the following output:

[15, 11, 13]
[15, 11, 13, 42]

To really achieve immutability, you should consider to also make your elements immutable.

Seelenvirtuose
  • 20,273
  • 6
  • 37
  • 66
  • I see, but if this is the fact, then when should I need the ImmutableList since it cannot really maintain immutability. – Boyu Zhang Jun 24 '20 at 09:05
  • @BoyuZhang Ideally you should aim to get as little mutability as possible. Those elements in the collection could be immutable themselves. – Amongalen Jun 24 '20 at 09:10
  • @BoyuZhang I thought my statement "To really achieve immutability, you should consider to also make your elements immutable" addresses your question? – Seelenvirtuose Jun 25 '20 at 05:41
  • Yeah, I appreciate that but I would mark the first answer since it gives more elaborating info on the design of the mentioned classes. – Boyu Zhang Jun 26 '20 at 00:57