讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議70:子列表只是原列表的一個視圖 >

建議70:子列表只是原列表的一個視圖

List接口提供了subList方法,其作用是返回一個列表的子列表,這與String類的subString有點類似,但它們的功能是否相同呢?我們來看如下代碼:


public static void main(Stringargs){

//定義一個包含兩個字符串的列表

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

c.add(\"A\");

c.add(\"B\");

//構造一個包含c列表的字符串列表

List<String>c1=new ArrayList<String>(c);

//subList生成與c相同的列表

List<String>c2=c.subList(0,c.size());

//c2增加一個元素

c2.add(\"C\");

System.out.println(\"c==c1?\"+c.equals(c1));

System.out.println(\"c==c2?\"+c.equals(c2));

}


c1是通過ArrayList的構造函數創建的,c2是通過列表的subList方法創建的,然後c2又增加了一個元素C,現在的問題是輸出的結果是什麼呢?列表c與c1、c2之間是什麼關係呢?

別忙著回答這個問題,我們先來回想一下String類的subString方法,看看它是如何工作的,代碼如下:


public static void main(Stringargs){

String str=\"AB\";

String str1=new String(str);

String str2=str.substring(0)+\"C\";

System.out.println(\"str==str1?\"+str1.equals(str1));

System.out.println(\"str==str2?\"+str.equals(str2));

}


很明顯,str與str1是相等的(雖然不是同一個對象,但用equals方法判斷是相等的),但它們與str2不相等,這毋庸置疑,因為str2在對像池中重新生成了一個新的對象,其表面值是ABC,那當然與str和str1不相等了。

說完了subString的小插曲,現在回到List是否相等的判斷上來。subList與subString的輸出結果是一樣的嗎?讓事實說話,運行結果如下:


c==c1?false

c==c2?true


很遺憾,與String類剛好相反,同樣是一個sub類型的操作,為什麼會相反呢?僅僅回答「為什麼」似不足以平復我們的驚訝,下面就從最底層的源代碼來進行分析。

c2是通過subList方法從c列表中生成的一個子列表,然後c2又增加了一個元素,可為什麼增加了一個元素還會相等呢?我們來看subList源碼:


public List<E>subList(int fromIndex, int toIndex){

return(this instanceof RandomAccess?

new RandomAccessSubList<E>(this, fromIndex, toIndex):

new SubList<E>(this, fromIndex, toIndex));

}


subList方法是由AbstractList實現的,它會根據是不是可以隨機存取來提供不同的SubList實現方式,不過,隨機存儲的使用頻率比較高,而且RandomAccessSubList也是SubList子類,所以所有的操作都是由SubList類實現的(除了自身的SubList方法外),那麼,我們就直接來看SubList類的代碼:


class SubList<E>extends AbstractList<E>{

//原始列表

private AbstractList<E>l;

//偏移量

private int offset;

//構造函數,注意list參數就是我們的原始列表

SubList(AbstractList<E>list, int fromIndex, int toIndex){

/*下標校驗,省略*/

//傳遞原始列表

l=list;

offset=fromIndex;

//子列表的長度

size=toIndex-fromIndex;

}

//獲得指定位置的元素

public E get(int index){

/*校驗部分,省略*/

//從原始字符串中獲得指定位置的元素

return l.get(index+offset);

}

//增加或插入

public void add(int index, E element){

/*校驗部分,省略*/

//直接增加到原始字符串上

l.add(index+offset, element);

/*處理長度和修改計數器*/

}

/*其他方法省略*/

}


通過閱讀這段代碼,我們就非常清楚subList方法的實現原理了:它返回的SubList類也是AbstractList的子類,其所有的方法如get、set、add、remove等都是在原始列表上的操作,它自身並沒有生成一個數組或是鏈表,也就是子列表只是原列表的一個視圖(View),所有的修改動作都反映在了原列表上。

我們例子中的c2增加了一個元素C,不過增加的元素C到了c列表上,兩個變量的元素仍保持完全一致,相等也就很自然了。

解釋完相等的問題,再回過頭來看看為什麼變量c與c1不相等。很簡單,因為通過ArrayList構造函數創建的List對像c1實際上是新列表,它是通過數組的copyOf動作生成的,所生成的列表c1與原列表c之間沒有任何關係(雖然是淺拷貝,但元素類型是String,也就是說元素是深拷貝的),然後c又增加了元素,因為c1與c之間已經沒有一毛錢的關係了,那自然是不相等了。

注意 subList產生的列表只是一個視圖,所有的修改動作直接作用於原列表。