Java泛型超詳細解讀 : super和extend


擦除

要理解 super 和 extends 的邊界問題,首先要理解消除。

先看一個有趣的例子:

結果如下:

true

明明 是 Integer 的,是 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM