我对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不理解上面的行为。
答案 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用于初始化不可变列表,无论是从头开始还是作为现有列表的防御性副本。