查询Collections.unmodifiableList()行为

时间:2016-07-21 14:22:49

标签: java collections

我对unmodifiableList的{​​{1}} API有疑问。

代码段:

Collections

我知道我无法修改不可修改的集合,因此我会收到错误

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());
        }       
    }
}

我希望在创建//list1.add("Flow"); 之前,我会获得list变量的只读列表。但即使在创建unmodifiableList之后,父列表中的更改也会反映在子list1

我无法修改子无法修改的列表,但仍然会收到父列表的修改。这对我来说很意外。

我知道修复是从父级创建新的Collection但是stil不理解上面的行为。

1 个答案:

答案 0 :(得分:6)

Collections JavaDoc清楚地解释了这一点:

  

返回指定列表的不可修改的视图。这种方法允许   模块为用户提供对内部列表的“只读”访问。   对返回列表的查询操作“通读”到指定的   列表,并尝试修改返回的列表,无论是直接还是通过   它的迭代器,导致UnsupportedOperationException。

(我鼓励“观点”)

所以当你这样做时:

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

...您无法通过调用unmodifiable.add(...)等来修改列表。但是,通过existingList.add(...)unmodifiable.get(...)等可以看到因调用unmodifiable.size()而导致的任何更改。< / p>

正如Javadoc所说,这意味着您可以为另一个对象提供列表的只读视图,您的类将继续修改该列表:

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));
    }
}

现在是另一个班级:

  • 可以致电contacts()并获取他们可以浏览的列表
  • 可以致电addContact()向列表中添加条目
  • 可以在列表视图中看到新联系人
  • 无法避免使用addContact()代替调用list.add(new Contact(...))

这在多线程程序中可能很危险。例如,如果在一个线程中,一个类使用不可修改的视图:

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

...在另一个线程中,ContactManager修改列表:

 contacts.remove(0);

...那么如果时间恰到好处,那么第一个例程将获得IndexOutOfBoundsException,因为查询长度和尝试读取最后一个条目后列表缩小了。

如果要初始化列表然后确保不对其进行进一步更改,可以通过限制指向可修改列表的变量范围来实现:

 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);
 }

responses的范围就是这种方法。方法之外的任何内容都无法到达responses,因此此方法之外的任何内容都无法修改列表。

如果您选择尽可能采用不可变对象的做法,这通常是一个好主意。

如果您的列表的内容将会发生变化,但您希望呼叫者获得一个不变的列表,则需要制作列表的防御性副本

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

(在这种情况下,我们不需要使返回的列表不可修改以保护自己,但它对调用者有帮助 - 如果他们调用add()set()他们将得到一个异常,而不是实际上不会更新“真实”列表的成功。

有些库可以帮助解决这类问题,例如,Guava有一些更流畅的API用于初始化不可变列表,无论是从头开始还是作为现有列表的防御性副本。