讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議72:生成子列表後不要再操作原列表 >

建議72:生成子列表後不要再操作原列表

前面說了,subList生成的子列表是原列表的一個視圖,那在subList執行完後,如果修改了原列表的內容會怎樣呢?視圖是否會改變呢?如果是數據庫視圖,表數據變更了,視圖當然會變了,至於subList生成的視圖是否會改變,還是從源碼上來看吧,代碼如下:


public static void main(String args){

List<String>list=new ArrayList<String>();

list.add("A");

list.add("B");

list.add("C");

List<String>subList=list.subList(0,2);

//原字符串增加一個元素

list.add("D");

System.out.println("原列表長度:"+list.size());

System.out.println("子列表長度:"+subList.size());

}


程序中有一個原始列表,生成了一個子列表,然後在原始列表中增加一個元素,最後打印出原始列表和子列表的長度,大家想一下,這段程序什麼地方會出現錯誤呢?

list. add("D")會報錯嗎?不會,subList並沒有鎖定原列表,原列表當然可以繼續修改。

難道有兩個size方法?正確,確實是size方法出錯了,輸出結果如下:


原列表長度:4

Exception in thread"main"java.util.ConcurrentModifcationException

at java.util.SubList.checkForComodification(AbstractList.java:752)

at java.util.SubList.size(AbstractList.java:625)


什麼?居然是subList的size方法出現了異常,而且還是並發修改異常?這沒道理呀,這裡根本就沒有多線程操作,何來並發修改呢?這個問題很容易回答,那是因為subList取出的列表是原列表的一個視圖,原數據集(代碼中的list變量)修改了,但是subList取出的子列表不會重新生成一個新列表(這點與數據庫視圖是不相同的),後面在對子列表繼續操作時,就會檢測到修改計數器與預期的不相同,於是就拋出了並發修改異常。

出現這個問題的最終原因還是在子列表提供的size方法的檢查上,還記得上面幾個例子中經常提到的修改計數器嗎?原因就在這裡,我們來看看size的源代碼:


public int size(){

checkForComodifcation();

return size;

}


其中的checkForComodification方法就是用於檢測是否並發修改的,代碼如下:


private void checkForComodification(){

//判斷當前修改計數器是否與子列表生成時一致

if(l.modCount!=expectedModCount)

throw new ConcurrentModificationException();

}


expectedModCount是從什麼地方來的呢?它是在SubList子列表的構造函數中賦值的,其值等於生成子列表時的修改次數(modCount變量)。因此在生成子列表後再修改原始列表,l.modCount的值就必然比expectedModCount大1,不再保持相等了,於是也就拋出了ConcurrentModificationException異常。

subList的其他方法也會檢測修改計數器,例如set、get、add等方法,若生成子列表後,再修改原列表,這些方法也會拋出ConcurrentModificationException異常。

對於子列表操作,因為視圖是動態生成的,生成子列表後再操作原列表,必然會導致「視圖」的不穩定,最有效的辦法就是通過Collections.unmodifiableList方法設置列表為只讀狀態,代碼如下:


public static void main(Stringargs){

List<String>list=new ArrayList<String>();

List<String>subList=list.subList(0,2);

//設置列表為只讀狀態

list=Collections.unmodifableList(list);

//對list進行只讀操作

doReadSomething(list)

//對subList進行讀寫操作

doReadAndWriteSomething(subList)

}


這在團隊編碼中特別有用,比如我生成了一個List,需要調用其他同事寫的共享方法,但是有一些元素是不能修改的,想想看,此時subList方法和unmodifiableList配合著使用是不是就可以解決我們的問題了呢?防禦式編程就是教我們如此做的。

這裡還有一個問題,數據庫的一張表可以有很多視圖,我們的List也可以有多個視圖,也就是可以有多個子列表,但問題是只要生成的子列表多於一個,則任何一個子列表就都不能修改了,否則就會拋出ConcurrentModificationException異常。

注意 subList生成子列表後,保持原列表的只讀狀態。