2016-07-21 18 views
-1

我對Collections的API有unmodifiableList API的查詢。對Collections.unmodifiableList()行爲的查詢

代碼片段:

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

我知道,我不能修改unmodifible收集,因此我會得到錯誤

//list1.add("Flow"); 

我預計我會得到只讀列表list可變,直到我創建unmodifiableList。但是即使在創建這個list1之後,父列表中的更改也會反映在子列表中list1

我無法修改子不可修改列表,但仍然收到父列表的修改。這對我來說是一個驚喜。

我知道修正是從父級創建新的集合,但stil不理解上述行爲。

+1

'Collections.unmodifiableList(list);'將創建一個'list'包裝器,它在所有修改方法中引發異常,並將所有讀取方法委託給列表本身。因此對'list'所做的更改將反映在'list1'中。我想你想創建一個副本,例如'Collections.unmodifiableList(new ArrayList <>(list))'(即你創建一個列表的副本並用不可修飾的包裝器包裝它)。 – Thomas

+4

這應該不是一個驚喜,文檔指出這只是原始集合的一個視圖。這就是爲什麼每當數據來源超出我們的控制範圍時,我們總是會做出防禦**副本**。不可修改的**視圖**僅在相反的情況下有用:當您控制原始數據源(基礎集合)並且您想將其展示給某人而不允許它們混淆原始列表時。 – biziclop

+0

「但stil不理解上述行爲」 - 你對這種行爲不瞭解的是什麼?正如biziclop提到的那樣,Javadoc明確指出該方法在內部列表上返回一個視圖,該視圖不能通過該視圖進行修改,但它從不會對原始列表進行任何說明。 –

回答

6

Collections的JavaDoc解釋這分明:

返回無法修改視圖指定列表的。該方法允許 模塊爲用戶提供對內部列表的「只讀」訪問權限。 在返回列表「通過讀取」到指定的 列表上查詢操作,並試圖修改返回的列表,無論是直接還是通過 其迭代器,都會導致UnsupportedOperationException異常。

(我底氣「意見」)

所以,當你這樣做:

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

...你不能調用unmodifiable.add(...)等修改名單,但所造成的調用existingList.add(...)任何改變將通過unmodifiable.get(...),unmodifiable.size(),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()一個條目添加到列表中
  • 能看到他們在列表中查看新聯繫人
  • 不能避免使用addContact() by代替呼叫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()他們會得到一個例外,而不是成功實際上不更新「真正」的名單。


有庫,可以用這種東西的幫助,例如番石榴用於初始化不變列出了一些更流暢的API,可以從頭做起,也作爲現有名單的防禦性副本。

+0

在最後一種情況下,也可以直接創建原始列表的不可變副本,即使用Guava的ImmutableList。 – biziclop

+1

@biziclop我想遠離圖書館,但番石榴值得一提,所以我添加了一段。 – slim