Reputation: 237
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.
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?
Upvotes: 0
Views: 549
Reputation: 3131
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.
Upvotes: 3
Reputation: 20618
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.
Upvotes: 2