擦除
要理解 super 和 extends 的邊界問題,首先要理解消除。
先看一個有趣的例子:
結果如下:
true
明明 a 是 Integer 的,b 是 String 的,為什么打印出他們是同一種類型呢?
因為在泛型代碼內部,無法獲取任何有關泛型參數類型的任何信息!,Java的泛型就是使用擦除來實現的,當你在使用泛型的時候,任何信息都被擦除,你所知道的就是你在使用一個對象。所以 List< Integer> 和 List< String> 在運行時,會被擦除成他們的原生類型List。
再通過這個例子加深一下擦除的理解:
我們可以看到,t.hello() 這里是不能夠編譯的。因為擦除的存在,main 中傳入的泛型,在TestHello 中編程了 Object,因此並不能跟 Hello這個類 綁定,也就調用不了 hello方法。
那能夠怎么讓object調用hello的方法?
可以給定泛型的邊界,把 類TestHello< T> 改為 TestHello< T extends Hello>,這個邊界聲明了T必須具有類型Hello或者從Hello導出的類型。
擦除帶來的問題
泛型不能用於顯性地引用運行時類型的操作之中,例如 轉型,instanceof 和 new 操作(包括 new一個對象,new一個數組),因為所有關於參數的類型信息都在運行時丟失了,所以任何在運行時需要獲取類型信息的操作都無法進行工作。
如下:
if(obj instanceof T);
T t = new T();
T[] ts = new T[10];
解決擦除帶來的問題
解決instanceof
使用 instanceof 會失敗,是因為類型信息已經被擦除,因此我們可以引入類型標簽Class< T>,就可以轉用動態的 isInstance()。
結果如下:
true
true
解決創建類型實例
解決辦法是使用工廠:
結果如下:
10
解決創建泛型數組
不能創建泛型數組的情況下,一般的解決方案是使用 ArrayList 代替泛型數組。因為ArrayList 內部就是使用數組,因此使用 ArrayList 能夠獲取數組的行為,和由泛型提供的編譯器的類型安全。
但是假如,某種特定的場合,你仍然要使用泛型數組,推薦的方式是使用 類型標簽+Array.newInstance 來實現,並用注解 @SuppressWarnings(“unchecked”) 抑制住警告:
結果如下:
10
邊界
正是因為有了擦除,把類型信息擦除了,所以,用無界泛型參數調用的方法只是那些可以用object調用的方法。但是,如果給定邊界,將這個參數限制為某個類型的子集,就可以使用這些類型子集來調用方法。
可見,類型T已經可以調用 Dog 的 shout方法 了。
當然,也可以指定多個邊界:
這里需要注意的是,extends 后面跟的第一個邊界,可以為類或接口,之后的均為接口。
通配符和泛型上界和下界
上界<? extends Class>
可見,指定了下邊界,卻 不能add 任何類型,甚至 Object 都不行,除了 null,因為 null 代表任何類型。List< ? extends Fruit> 可以解讀為,“具有任何從Fruit繼承的類型”,但實際上,它意味着,它沒有指定具體類型。對於編譯器來說,當你指定了一個 List< ? extends Fruit>,add 的參數也變成了“? extends Fruit”。因此編譯器並不能了解這里到底需要哪種 Fruit 的子類型,因此他不會接受任何類型的 Fruit。
然而,contain 和 indexof 卻能執行,這是因為,這兩個方法的參數是 Object,不涉及任何的通配符,所以編譯器允許它調用。
list.get(0) 能夠執行是因為,當item在此list存在時,編譯器能夠確定他是Apple的子類,所以能夠安全獲得。
下界<? super Class>
< ? super Class>表示,指定類的基類。
這里可以看到,list.add(new Fruit()) 這句不能編譯成功,這是因為 List< ? super Apple> 表示“具有Apple的父類的列表”。但是為什么 add(new Fruit())不能成功 呢?正是因為?代表Apple的父類,但是編譯器不知道你要添加哪種Apple的父類,因此不能安全地添加。
對於 super,get 返回的是 Object,因為編譯器不能確定列表中的是Apple的哪個子類,所以只能返回 Object。
PECS原則
如果要從集合中讀取類型T的數據,並且不能寫入,可以使用 ? extends 通配符;(Producer Extends)
如果要從集合中寫入類型T的數據,並且不需要讀取,可以使用 ? super 通配符;(Consumer Super)
如果既要存又要取,那么就不要使用任何通配符。
劉劍峰 的博客地址:http://blog.csdn.net/jeffleo