I want a List
whose elements cannot be removed nor added. I thought I'd found the answer with Collections.unmodifiableList in Java 8. I pass my original list and get back a supposedly unmodifiable list.
Yet when I delete an element from the original list, my unmodifiable list is modified. What is going on?
See this demo code. My unmodifiable list shrinks from 3 elements 2 when deleting from original.
String dog = "dog";
String cat = "cat";
String bird = "bird";
List< String > originalList = new ArrayList<>( 3 );
originalList.add( dog );
originalList.add( cat );
originalList.add( bird );
List< String > unmodList = Collections.unmodifiableList( originalList );
System.out.println( "unmod before: " + unmodList ); // Yields [dog, cat, bird]
originalList.remove( cat ); // Removing element from original list affects the unmodifiable list?
System.out.println( "unmod after: " + unmodList ); // Yields [dog, bird]
The solution to this problem is quite simple and is highlighted in the following code. final List<String> modifiable = new ArrayList<>(); modifiable. add("Java"); modifiable. add("is"); // Here we are creating a new array list final List<String> unmodifiable = Collections.
The unmodifiableList() method of java. util. Collections class is used to return an unmodifiable view of the specified list. This method allows modules to provide users with “read-only” access to internal lists.
If you create a List and pass it to the Collections. unmodifiableList method, then you get an unmodifiable view. The underlying list is still modifiable, and modifications to it are visible through the List that is returned, so it is not actually immutable.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With