The unmodifiableList Is Backed By Original List
That unmodifiableList method in Collections utility class does not create a new list, it creates a pseudo-list backed by the original list. Any add or remove attempts made through the "unmodifiable" object will be blocked, thus the name lives up to its purpose. But indeed, as you have shown, the original list can be modified and simultaneously affects our secondary not-quite-unmodifiable list.
This is spelled out in the class documentation:
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.
That fourth word is key: view
. The new list object is not a fresh list. It is an overlay. Just like tracing paper or transparency film over a drawing stops you from making marks on the drawing, it does not stop you from going underneath to modify the original drawing.
Moral of the Story: Do not use Collections.unmodifiableList for making defensive copies of lists.
Ditto for Collections.unmodifiableMap, Collections.unmodifiableSet, and so on.
Google Guava
Instead of the Collections
class, for defensive programming I recommend using the Google Guava library and its ImmutableCollections facility.
You can make a fresh list.
public static final ImmutableList<String> ANIMALS = ImmutableList.of(
dog,
cat,
bird );
Or you can make a defensive copy of an existing list. In this case you will get a fresh separate list. Deleting from the original list will not affect (shrink) the immutable list.
ImmutableList<String> ANIMALS = ImmutableList.copyOf( originalList ); // defensive copy!
But remember, while the collection’s own definition is separate, the contained objects are shared by both the original list and new immutable list. When making that defensive copy, we are not duplicating the "dog" object. Only one dog object remains in memory, both lists contain a reference pointing to the same dog. If the properties in the "dog" object are modified, both collections are pointing to that same single dog object and so both collections will see the dog’s fresh property value.