繁体   English   中英

查询Collections.unmodifiableList()行为

[英]Query on Collections.unmodifiableList() behaviour

我有一个关于Collections unmodifiableList API的查询。

程式码片段:

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之后,父列表中的更改也会反映在子list1

我无法修改子级不可修改列表,但仍收到父级列表的修改。 对我来说是一个惊喜。

我知道该修复程序正在从父级创建新的Collection,但是Stil无法理解上述行为。

Collections JavaDoc对此进行了清楚的解释:

返回指定列表的不可修改视图 此方法允许模块为用户提供对内部列表的“只读”访问权限。 对返回列表的查询操作“读取”到指定列表,并尝试直接或通过其迭代器修改返回列表,将导致UnsupportedOperationException。

(我大胆地提出了“看法”)

因此,当您这样做时:

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

...你不能调用修改列表unmodifiable.add(...)等,但所造成的调用任何改变existingList.add(...)将通过可见unmodifiable.get(...) unmodifiable.size()

正如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()将条目添加到列表中
  • 可以在列表视图中看到新联系人
  • 无法避免通过调用list.add(new Contact(...))使用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,可以从头开始或作为现有列表的防御性副本来初始化不可变列表。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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