不詩意的女程序媛不是好廚師~ 轉載請註明出處,From李詩雨---blog.csdn.net/cjm24848365…】java
今天要講的就是有一點點難度的東西了,理解一個東西最好的方式就是: 本身找到類似的情景做類比→畫圖幫助理解→本身敲代碼理解→反思回味。 畢竟只有通過本身大腦思考的東西,才能爲己所用。程序員
筆記我仍是整理的很細,也很好理解。那麼今天,讓我來繼續學習剩下的三點內容吧~♥安全
? 和關鍵字extends或者super在一塊兒其實就是泛型的高級應用:通配符。函數
對於它們的使用送你們八字咒語: 「上界不存下界不取」學習
這個咒語什麼意思,別急讓我來慢慢講:this
它表示傳遞給方法的參數,必須是X的子類(包括X自己), 是類型的上界。spa
因此上界 就是 針對 ?extends X 來講的。.net
咱們來讓代碼本身說它是什麼意思~ 有這樣的幾個類:3d
public class Animal {
//...
}
複製代碼
public class Pet extends Animal {
//...
}
複製代碼
public class Dog extends Pet {
//...
}
複製代碼
又有這樣一個方法:code
public static void print(Generic<? extends Animal> a){
System.out.println(a.getData().getName());
}
複製代碼
咱們把這個方法和 ? extends X 對應一下,那此時的Animal類就至關因而X,因爲它表示類型的上界,因此,咱們能夠傳Animal及其子類。
再畫個圖來幫助咱們理解一下 :
如今,?extends X 的含義你們應該已經理解,而且知道怎麼傳遞類型參數了吧。
知道了魔咒 「上界不存」 中的 「上界」 的意思了,那 「不存」 又是什麼意思呢? 繼續往下看: 如今有一個Generic的泛型類,它提供了get和set類型參數變量的方法:
public class Generic<T> {
//...
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
複製代碼
我想經過set方法往裏面放數據,可是,set方法卻不容許被調用!編譯不經過!
既然 ? extends X 表示類型的上界,類型參數是X的子類,
因此:? extends X 主要用於安全地訪問數據,能夠訪問X及其子類型,可是不能寫入非null的數據。
這就是 「不存」 的意思了,如今,「上界不存」 的咒語你理解了嗎?
它表示傳遞給方法的參數,必須是X的超類(包括X自己), 與 ? extends X相反,它表示類型下界。
因此下界 就是 針對 ?superX 來講的l了。
咱們用相似的方法來理解一下 「下界不取」 的意思。
仍是上面的 Animal、Pet 和Dog類。 咱們又有一個方法:
public static void printSuper(Generic<? super Pet> a){
System.out.println(a.getData());
}
複製代碼
那這個方法中的 Pet 就至關於 ? super X 裏的X,因爲它表示類型的下界,因此,咱們能夠傳Pet及其超類。 一樣的,咱們也來畫個圖:
那 「下界不取」 中的 不取 又是什麼道理呢? 咱們把以前的代碼稍做改變:
對此現象咱們 能夠這樣來理解: ? super X 表示類型的下界,類型參數是X的超類(包括X自己)。
因此,? super X 主要用於安全地寫入數據,能夠寫入X及其子類型。可是讀取只能獲得Object,並無什麼實際意義,因此咱們就沒有太大必要去get了。
因此 「下界不取」 的 「不取」 含義也就在於此了。
綜上,咱們就知道 「上界不存,下界不取」 的真實含義其實就是: 都是出於 安全考慮,
說的官方一點,這裏其實就涉及到 PESC 原則了。 即 Producer Extends Consumer Super 若是參數化類型表示一個生產者,就使用<? extends T>;若是它表示一個消費者,就使用<? super T>
即: 只讀不可寫時,使用Generic<? extends Animal>:Producer 只寫不可讀時,使用Generic<? super Pet>:Consumer
無規矩不成方圓,其實泛型的使用也是有侷限性和約束的。 下面就讓咱們來一一看一下,哪些地方是使用泛型時須要格外注意的~
咱們都以 Generic 這個泛型類來說:
而要用 基本數據類型對應的包裝類才能夠。 代碼爲證:
這個要稍微作個解釋,先問你們一個問題 : (PS:其實這一點也能夠從類型擦除角度來理解)
Generic<String> stringG=new Generic<>();
Generic<Boolean> booleanG=new Generic<>();
//輸出會是什麼?
System.out.println(stringG.getClass()==booleanG.getClass());
//輸出又會是什麼
System.out.println("stringG.getClass()的name是:"+stringG.getClass().getName());
System.out.println("booleanG.getClass()的name是:"+booleanG.getClass().getName());
複製代碼
答案是:
這也就說明 運行時的類型 檢測,只適用於 原始類型 Generic ,而不是Generic< String> 、Generic< Boolean>.。
因此下面這個代碼就是不正確的, instanceof 在 泛型上 是不能使用的,只能用於原始類型。
也就是:
再來:
public <T extends Throwable> void doSth(T x) throws T{
try{
}catch(Throwable e){
throw x;
}
}
複製代碼
雖然不容許咱們直接捕獲泛型類的對象,可是咱們可把 泛型類的對象 throw 出去呀,就是這麼機智✌~
最後咱們再來提一提泛型中的類型擦除吧。
泛型思想早在C++語言的模板(Template)中就開始生根發芽,在Java語言尚未出現泛型的版本時,只能經過Object是全部類型的父類和類型強制轉換兩個特色的配合來實現類型泛化。 因爲Java語言裏面全部的類型都繼承於java.lang.Object,因此Object轉型成任何對象都是有可能的。可是也由於有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object究竟是個什麼類型的對象。在編譯期間,編譯器沒法檢查這個Object的強制轉型是否成功,若是僅僅依賴程序員去保障這項操做的正確性,許多ClassCastException的風險就會轉嫁到程序運行期之中。
泛型技術在C和Java之中的使用方式看似相同,但實現上卻有着根本性的分歧。C裏面的泛型是真實泛型,而Java中的泛型實際上是僞泛型。 這點怎麼說呢?
至於爲何java爲何採用 類型擦除 來實現 泛型? 這是由於java 1.5以前並無泛型這個概念,泛型出現後,爲了兼容以前的版本,就採用了類型擦除折中的策略:編譯時對泛型要求嚴格,運行時卻把泛型擦除了。
看下面的代碼:
這是由於參數List<Integer>和List<String>編譯以後都被擦除了,變成了同樣的原生類型List。因此此處傳入的實際上是同一個類型。不是重載。
【後記】 以初學者的心態去重學java,我忽然發現本身不懂的愈來愈多了,問號一個接一個的在腦子裏跳舞。就拿泛型來講,我仍是有不少要去學習和探究的地方,這裏也只是作個學習後的整理,等下一次我有了新的收穫後再繼續分享,但願能夠和你們一塊兒,進步進步再進一步~
積累點滴,作好本身~