简体   繁体   中英

Query on Collections.unmodifiableList() behaviour

I have a query regarding unmodifiableList API of Collections .

Code snippet:

import java.util.*;

public class ReadOnlyList{
    public static void main(String args[]){
        List<String> list = new ArrayList<String>();
        list.add("Stack");
        list.add("Over");
        List list1 = Collections.unmodifiableList(list);
        //list1.add("Flow");
        list.add("Flow");
        System.out.println("sizes:1:2:"+list.size()+":"+list1.size());
        for (Iterator it = list1.iterator();it.hasNext();){
            System.out.println("elements:"+it.next());
        }
        System.out.println("__________");
        list.remove("Flow");
        for (Iterator it = list1.iterator();it.hasNext();){
            System.out.println("elements:"+it.next());
        }       
    }
}

I know that I can't modify the unmodifible collection and hence I will get error for

//list1.add("Flow");

I expected that I will get read-only list of list varaible until the point I create unmodifiableList . But even after this list1 creation, the changes in parent list are reflected in child list1

I can't modify child unmodifible list but still I receive modifications of parent list. It's a surprise for me.

I know that fix is creating new Collection from parent but stil did not understand above behaviour.

The Collections JavaDoc explains this clearly:

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.

(I emboldened "view")

So when you do:

 List<Foo> unmodifiable = Collections.unmodifiableList(existingList);

... you cannot modify the list by calling unmodifiable.add(...) etc. But any changes caused by calls to existingList.add(...) will be visible through unmodifiable.get(...) , unmodifiable.size() etc.

As the Javadoc says, this means you can give another object a read-only view of a list that your class will continue to modify:

class ContactManager {

   List<Contact> contacts = new ArrayList<>();        

    public List<Contact> contacts() {
        return Collection.unmodifiableList(contacts);
    }

    public void addContact(String name) {
       contacts.add(new Contact(name));
    }
}

Now another class:

  • can call contacts() and get a List they can browse
  • can call addContact() to add an entry to the list
  • can see the new contact in their view of the list
  • cannot avoid using addContact() by instead calling list.add(new Contact(...))

This could be dangerous in a multi-threaded program. For example, if in one thread, a class works with the unmodifiable view:

 List<contacts> contactsView = contactManager.contacts();
 int lastEntry = contactsView.size() - 1;
 contactsView.get(lastEntry);

... while in another thread, the ContactManager modifies the list:

 contacts.remove(0);

... then if the timing is just-so, the first routine will get an IndexOutOfBoundsException , because the list has shrunk between querying the length and trying to read the last entry.


If you want to initialise a list then ensure you don't make further changes to it, you can achieve that by restricting the scope of the variable pointing to the modifiable list:

 private List<String> validResponses() {
       List<String> responses = new ArrayList<>();
       values.add("Yes");
       values.add("Agree")
       values.add("No");
       values.add("Disagree");
       return Collections.unmodifiableList(responses);
 }

The scope of responses is just this method. Nothing outside the method can reach responses , and so nothing outside this method can modify the list.

This is generally a good idea, if you choose to adopt the practice of using immutable objects wherever possible.


If you have a list whose contents are going to be changing, but you want callers to get an unchanging list, you need to make a defensive copy of the list.

public List<Contact> contacts() {
     List<Contact> copy = new ArrayList<>();
     List.copy(contacts, copy);
     return Collections.unmodifiableMap(copy);
}

(In this case, we didn't need to make the returned list unmodifiable to protect ourselves, but it's helpful to the caller -- if they call add() or set() they'll get an exception, instead of a success that doesn't actually update the "real" list.


There are libraries that can help with this kind of thing, for example Guava has some more fluent APIs for initialising immutable lists, either from scratch or as defensive copies of existing lists.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM