Why can we change an unmodifiable list if we have an original one?

After examining the Collections code, I learned that when we use the unmodifiableList(List list) or unmodifiableCollection(Collection c) method, it does not create a new object, but returns a reference to the same object and overrides methods that can modify List [ add , addall , remove , retainAll ...]
So I did this test:

 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 [ 1,2 ] .
Now the question is, why does it refer to the same object? Why doesn't he create a new object?

+4
source share
7 answers

(queston answer below)

When you create an unmodifiable list, the goal is that it should not be changed by people other than you, that is, API clients.

the unmodifiableList(..) method creates a new object of the UnmodifiableList type (but this is not a public class), which receives the original list and delegates to it all methods except for the methods that modified it.

Point as indicated in the documentation:

Returns an unmodifiable representation of the specified list. This method allows modules to provide users with access to internal read-only lists.

So, an example: you have a List devices that your API has discovered and can work, and you want to provide them with a client of your API. But he should not change them. So you have two options:

  • give him a deep copy of your List , so even if he changes it, it will not change your list.
  • give him an unmodifiable collection - he cannot change it, and you get rid of creating a new collection.

And now the answer to the title of your question - an unmodifiable list - is a kind of original collection. Therefore, if you need to add a new element to it - let's say you find a new device that was simply connected, customers will be able to see it in their unchanged form.

+9
source

Now the point is why does it refer to the same object? Why not create a new object?

Performance. It just does not scale to make a full copy. It would be a linear temporary operation to make a full copy, which is obviously impractical. In addition, as others have already noted, the fact is that you can pass a link to an unmodifiable list without worrying that it has changed. This is very useful for multithreaded programs.

+3
source

From the documentation:

public static List unmodifiableList(List list)

Returns an unmodifiable representation of the specified list. This method allows modules to provide users with read-only access to internal lists. Query operations on the returned “read” list to the specified list and attempts to modify the returned list, whether direct or through its iterator, throw an UnsupportedOperationException.

+2
source

I believe the secret lies in the implementation details ... Collection.unmodifiableList () will simply provide you with a styled, modifiable list. I mean, an unmodifiable list contains a link to the modifiable list inside.

0
source

Bozho's approved answer is correct. Here is a bit more information, sample code, and suggested alternative.

Unmodified list maintained by source list

This unmodifiableList method in the Collections class does not create a new list; it creates a pseudo-list supported by the original list. Any additions or deletions of attempts made using the "non-editable" object will be blocked, so the name matches its purpose. But really, as you have shown, the original list can be changed and at the same time affect our secondary not entirely unmodifiable list.

This is stated in the class documentation:

Returns an unmodifiable representation of the specified list. This method allows modules to provide users with access to internal read-only lists. Query operations on the returned “read” list to the specified list and attempts to modify the returned list, whether direct or through its iterator, throw an UnsupportedOperationException.

This fourth word is the key: view . The new list object is not a new list. This is an overlay. Just as tracing paper or transparent film above the pattern stops you from creating marks on the drawing, this does not prevent you from switching to changing the original drawing.

The moral of the story: do not use Collections.unmodifiableList to create protected copies of lists.

Ditto for Collections.unmodifiableMap , Collections.unmodifiableSet etc.

Here is another example demonstrating the problem.

 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 .

You can create a fresh list.

 public static final ImmutableList<String> ANIMALS = ImmutableList.of( dog, cat, bird ); 

Or you can create a protective copy of an existing list. In this case, you will receive a new separate list. Removing from the source list will not affect (compress) the immutable list.

 ImmutableList<String> ANIMALS = ImmutableList.copyOf( originalList ); // defensive copy! 

But remember that while the collection’s own definition is separate, the contained objects are shared by both the original list and the new immutable list. When creating this protective copy, we do not duplicate the dog object. Only one dog object remains in memory, both lists contain a link pointing to the same dog. If the properties in the dog object are changed, both collections point to the same object in the same dog, so both collections will see the value of the new dog value.

0
source

I found one way to do this:

 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); } 
0
source
  • you need to create a new list object only when the original object is changed, and you need a backup when someone corrupts it, and you can replace it with a new object.

  • To create an ummodifiable object, I will wrap the original object and prevent adding, removing by throwing an exception. but I know, I can change every object in the list. For example, if you have a human object in a list, we can still change the name of the person object in the list.

-2
source

All Articles