讀古今文學網 > 編寫高質量代碼:改善Java程序的151個建議 > 建議96:不同的場景使用不同的泛型通配符 >

建議96:不同的場景使用不同的泛型通配符

Java泛型支持通配符(Wildcard),可以單獨使用一個「?」表示任意類,也可以使用extends關鍵字表示某一個類(接口)的子類型,還可以使用super關鍵字表示某一個類(接口)的父類型,但問題是什麼時候該用extends,什麼時候該用super呢?

(1)泛型結構只參與「讀」操作則限定上界(extends關鍵字)

閱讀如下代碼,想想看我們的業務邏輯操作是否還能繼續:


public static<E>void read(List<?super E>list){

for(Object obj:list){

//業務邏輯操作

}

}


從List列表中讀取元素的操作(比如一個數字列表中的求和計算),你覺得方法read能繼續寫下去嗎?

答案是:不能,我們不知道list到底存放的是什麼元素,只能推斷出是E類型的父類(當然,也可以是E類型,下同,不再贅述),但問題是E類型的父類是什麼呢?無法再推斷,只有運行時才知道,那麼編碼期就完全無法操作了。當然,你可以把它當作是Object類來處理,需要時再轉換成E類型——這完全違背了泛型的初衷。

在這種情況下,「讀」操作如果期望從List集合中讀取數據就需要使用extends關鍵字了,也就是要界定泛型的上界,代碼如下:


public static<E>void read(List<?extends E>list){

for(E e:list){

//業務邏輯處理

}

}


此時,已經推斷出List集合中取出的是E類型的元素。具體是什麼類型的元素就要等到運行時才能確定了,但它一定是一個確定的類型,比如read(Arrays.asList("A"))調用該方法時,可以推斷出List中的元素類型是String,之後就可以對List中的元素進行操作了,如加入到另外的List<E>集合中,或者作為Map<E,V>的鍵等。

(2)泛型結構只參與「寫」操作則限定下界(使用super關鍵字)先看如下代碼是否可以編譯:


public static void write(List<?extends Number>list){

//加入一個元素

list.add(123);

}


編譯失敗,失敗的原因是list中的元素類型不確定,也就是編譯器無法推斷出泛型類型到底是什麼,是Integer類型?是Double?還是Byte?這些都符合extends關鍵字的定義,由於無法確定實際的泛型類型,所以編譯器拒絕了此類操作。

在此種情況下,只有一個元素是可以add進去的:null值,這是因為null是一個萬用類型,它可以是所有類的實例對象,所以可以加入到任何列表中。

Object是否也可以?不可以,因為它不是Number子類,而且即使把list變量修改為List<?extends Object>類型也不能加入,原因很簡單,編譯器無法推斷出泛型類型,加什麼元素都是無效的。

在這種「寫」操作的情況下,使用super關鍵字限定泛型類型的下界才是正道,代碼如下:


public static void write(List<?super Number>list){

list.add(123);

list.add(3.14);

}


甭管它是Integer類型的123,還是Float類型的3.14,都可以加入到list列表中,因為它們都是Number類型,這就保證了泛型類的可靠性。

對於是要限定上界還是限定下界,JDK的Collections.copy方法是一個非常好的例子,它實現了把源列表中的所有元素拷貝到目標列表中對應的索引位置上,代碼如下:


public static<T>void copy(List<?super T>dest, List<?extends T>src){

for(int i=0;i<srcSize;i++)

dest.set(i, src.get(i));

}


源列表是用來提供數據的,所以src變量需要限定上界,帶有extends關鍵字。目標列表是用來寫入數據的,所以dest變量需要界定上界,帶有super關鍵字。

如果一個泛型結構即用作「讀」操作又用作「寫」操作,那該如何進行限定呢?不限定,使用確定的泛型類型即可,如List<E>。