Why can we change the unmodifiable list if we have the original one?
By looking at the code of Collections
class, i got to know that when we are using the method unmodifiableList(List list)
or unmodifiableCollection(Collection c)
it is not creating a new object but it is re开发者_开发知识库turning the reference of the same object and overriding the methods which can modify the List
[ add
, addall
, remove
, retainAll
... ]
List modifiableList = new ArrayList();
modifiableList.add ( 1 );
List unmodifiableList = Collections.unmodifiableList( modifiableList );
// unmodifiableList.add(3); // it will throw the exception
modifiableList.add ( 2 );
System.out.println( unmodifiableList );
result is [ 1,2 ]
.
(answer of the queston at the bottom)
When you create an unmodifiable list, the purpose is that it should not be modified by people other than you - i.e. clients of an API.
the method unmodifiableList(..)
creates a new object of type UnmodifiableList
(but this is not a public class), which gets the original list, and delegates all methods to it except the methods which would modify it.
The point is, as stated in the documentation:
Returns an unmodifiable view of the specified list. This method allows modules to provide users with "read-only" access to internal lists.
So, an example: You have a List
of devices that your API has detected and can operate, and you want to give them a client of your API. But he is not supposed to change them. So you have two options:
- give him a deep copy of your
List
, so that even if he modifies it, this does not change your list - give him an unmodifiable collection - he can't modify it, and you spare the creation of a new collection.
And now here comes the answer to the title of your question - the unmodifiable list is a view of the original collection. So if you need to add a new item to it - say, you have discovered a new device that was just plugged-in, the clients will be able to see it in their unmodifiable view.
Now the point is why it is referring to the same object? Why it don't create a new object?
Performance. It just doesn't scale to make a full copy. It would be a linear time operation to make a full copy which obviously isn't practical. Also, as others already noted, the point is that you can pass the reference of the unmodifiable list around without having to worry that it gets changed. This is very helpful for multithreaded programs.
From documentation:
public static List unmodifiableList(List list)
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.
The accepted Answer by Bozho is correct. Here's a bit more info, example code, and a suggested alternative.
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.
Here is another example demonstrating the issue.
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]
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.
I believe the secret lies in implementation details... Collection.unmodifiableList() will simply give you decorated modifiable list. I mean unmodifiable list contains reference to modifiable list internally.
I Found one way to do this is
List unmodifiableList = Collections.unmodifiableList( new ArrayList(modifiableList));
List<String> strings = new ArrayList<String>();
// unmodifiable.add("New string");
strings.add("Aha 1");
strings.add("Aha 2");
List<String> unmodifiable = Collections.unmodifiableList(strings);
List<String> immutableList = Collections.unmodifiableList(new ArrayList<>(strings));
// Need some way to fix it so that Strings does not Modify
strings.add("Aha 3");
strings.add("Aha 4");
strings.remove(0);
for (String str : unmodifiable) {
System.out.println("Reference Modified :::" + str);
}
for (String str : immutableList) {
System.out.println("Reference Modified :::" + str);
}
you should go for creating new Object of a list, only when the original Object is going to be changed and you need a backup , when someone corrupts it , u can replace by new Object.
To create a ummodifiable object, i will wrap the original object and prevent add ,remove by throwing exception. but u know ,i can change each object present in the list .like if u have a person object in an umodifiable list , i can still change the name of the person object in the list.
精彩评论