《重學Java系列》之 泛型(下)

不詩意的女程序媛不是好廚師~ 轉載請註明出處,From李詩雨---blog.csdn.net/cjm24848365…java

在這裏插入圖片描述
《重學Java系列》之 泛型(上)中咱們已經知道了①什麼是泛型,②爲何要有泛型,③泛型類、泛型接口 和泛型方法,④給泛型變量增長約束,⑤泛型類型的繼承規則。

今天要講的就是有一點點難度的東西了,理解一個東西最好的方式就是: 本身找到類似的情景做類比→畫圖幫助理解→本身敲代碼理解→反思回味。 畢竟只有通過本身大腦思考的東西,才能爲己所用。程序員

筆記我仍是整理的很細,也很好理解。那麼今天,讓我來繼續學習剩下的三點內容吧~♥安全

6.泛型通配符

在這裏插入圖片描述
你見過上面☝這樣的代碼嗎

? 和關鍵字extends或者super在一塊兒其實就是泛型的高級應用:通配符。函數

  • ? extends X 表示類型的上界,類型參數是X的子類
  • ? super X 表示類型的下界,類型參數是X的超類

對於它們的使用送你們八字咒語: 「上界不存下界不取」學習

這個咒語什麼意思,別急讓我來慢慢講:this

(1). 先看看 ? extends X

它表示傳遞給方法的參數,必須是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的子類,

  • 那麼能夠確定的是:get方法返回的必定是個X(不論是X或者X的子類)編譯器是能夠肯定知道的。因此,get它容許調用。
  • 可是set方法只知道傳入的是個X,至於具體是X的那個子類,編譯器不知道。因此,出於安全考慮,編譯器乾脆就不作了。

因此:? extends X 主要用於安全地訪問數據,能夠訪問X及其子類型,可是不能寫入非null的數據。

這就是 「不存」 的意思了,如今,「上界不存」 的咒語你理解了嗎?

(2). 再看看 ? super X

它表示傳遞給方法的參數,必須是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說:「我就是底線,不能再比我低了,不然我會生氣的,後果很嚴重!哼!☹」

「下界不取」 中的 不取 又是什麼道理呢? 咱們把以前的代碼稍做改變:

在這裏插入圖片描述
咱們發現 此時 set方法能夠被調用的,且能傳入的參數只能是X或者X的子類。 可是get方法只會返回一個Object類型的值,並無什麼實際意義。

對此現象咱們 能夠這樣來理解: ? super X 表示類型的下界,類型參數是X的超類(包括X自己)。

  • 那麼就能夠確定的說,get方法返回的必定是個X的超類,可是究竟是哪一個超類?不知道。可是能夠確定的說,Object必定是它的超類,由於Object是全部類的超類,因此get方法返回Object。可是這樣取出的全是Object其實並無什麼意義。
  • 對於set方法來講,編譯器不知道它須要的確切類型,可是X和X的子類能夠安全的轉型爲X。因此,此時set是被容許的。

因此,? super X 主要用於安全地寫入數據,能夠寫入X及其子類型。可是讀取只能獲得Object,並無什麼實際意義,因此咱們就沒有太大必要去get了。

因此 「下界不取」「不取」 含義也就在於此了。

綜上,咱們就知道 「上界不存,下界不取」 的真實含義其實就是: 都是出於 安全考慮,

  • 「?extends X」 主要用於安全地訪問數據。它只用來讀數據,不用來存放數據。
  • ? super X 主要用於安全地寫入數據。它只用來寫入數據,不用來取出數據。

說的官方一點,這裏其實就涉及到 PESC 原則了。 即 Producer Extends Consumer Super 若是參數化類型表示一個生產者,就使用<? extends T>;若是它表示一個消費者,就使用<? super T>

即: 只讀不可寫時,使用Generic<? extends Animal>:Producer 只寫不可讀時,使用Generic<? super Pet>:Consumer

7.泛型使用的侷限性

無規矩不成方圓,其實泛型的使用也是有侷限性和約束的。 下面就讓咱們來一一看一下,哪些地方是使用泛型時須要格外注意的~

咱們都以 Generic 這個泛型類來說:

在這裏插入圖片描述

1. 泛型中不能用 基本數據類型 來實例化類型參數,

而要用 基本數據類型對應的包裝類才能夠。 代碼爲證:

在這裏插入圖片描述

2. 運行時 只看 原始類型

這個要稍微作個解釋,先問你們一個問題 : (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());

複製代碼

答案是:

在這裏插入圖片描述
哇,stringG.getClass() 和 booleanG.getClass() 居然相等,他們對應的是同一個類型:Generic ,而不是 Generic< String> ,Generic< Boolean>.

這也就說明 運行時的類型 檢測,只適用於 原始類型 Generic ,而不是Generic< String> 、Generic< Boolean>.。

因此下面這個代碼就是不正確的, instanceof 在 泛型上 是不能使用的,只能用於原始類型。

在這裏插入圖片描述

3. 不能在靜態域或方法中引用類型變量

在這裏插入圖片描述
這是爲何呢: 由於泛型是要在對象建立的時候才知道是什麼類型的,而對象建立的代碼執行順序是,先static的部分,而後纔是構造函數。 因此在對象初始化以前static的部分就已經執行了,若是你在靜態部分引用了泛型,那麼毫無疑問虛擬機根本不知道是什麼東西,由於這個時候類尚未初始化。

4. 不能實例化類型變量,

也就是:

在這裏插入圖片描述
這個T原本就是虛的,都沒有摸清底細,你就亂new 確定是不行的。

5. 不能捕獲泛型類的實例

在這裏插入圖片描述
因此,要注意一點:泛型類是不能extends Exception/Throwable的。

再來:

在這裏插入圖片描述
咱們是不能直接捕獲泛型類的對象的。那若是咱們想捕獲 泛型 的異常就沒有辦法了嗎?咱們但是偉大的碼農,因此方法仍是有的:

public <T extends Throwable> void doSth(T x) throws T{
        try{

        }catch(Throwable e){
            throw x;
        }
    }
複製代碼

雖然不容許咱們直接捕獲泛型類的對象,可是咱們可把 泛型類的對象 throw 出去呀,就是這麼機智✌~

7.淺談類型擦除

最後咱們再來提一提泛型中的類型擦除吧。

泛型思想早在C++語言的模板(Template)中就開始生根發芽,在Java語言尚未出現泛型的版本時,只能經過Object是全部類型的父類類型強制轉換兩個特色的配合來實現類型泛化。 因爲Java語言裏面全部的類型都繼承於java.lang.Object,因此Object轉型成任何對象都是有可能的。可是也由於有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object究竟是個什麼類型的對象。在編譯期間,編譯器沒法檢查這個Object的強制轉型是否成功,若是僅僅依賴程序員去保障這項操做的正確性,許多ClassCastException的風險就會轉嫁到程序運行期之中。

泛型技術在C和Java之中的使用方式看似相同,但實現上卻有着根本性的分歧。C裏面的泛型是真實泛型,而Java中的泛型實際上是僞泛型。 這點怎麼說呢?

  • 是這樣的,在C裏面泛型不管在程序源碼中、編譯後的IL中(Intermediate Language,中間語言,這時候泛型是一個佔位符),或是運行期的CLR中,都是切實存在的,List<Integer>與List<String>就是兩個不一樣的類型,它們在系統運行期生成,有本身的虛方法表和類型數據,這種實現稱爲類型膨脹,基於這種方法實現的泛型稱爲真實泛型
  • Java中的泛型則不同,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經替換爲原來的原生類型(Raw Type,也稱爲裸類型)了,而且在相應的地方插入了強制轉型代碼,所以,對於運行期的Java語言來講,ArrayList<Integer>與ArrayList<String>就是同一個類,因此泛型技術其實是Java語言的一顆語法糖,Java語言中的泛型實現方法稱爲類型擦除,基於這種方法實現的泛型稱爲僞泛型

至於爲何java爲何採用 類型擦除 來實現 泛型? 這是由於java 1.5以前並無泛型這個概念,泛型出現後,爲了兼容以前的版本,就採用了類型擦除折中的策略:編譯時對泛型要求嚴格,運行時卻把泛型擦除了。

看下面的代碼:

在這裏插入圖片描述
編譯不經過:
在這裏插入圖片描述
按理說一個方法,我傳入的參數類型不同的話,是能夠重載的呀?那這裏爲何會呢? 上面這段代碼之因此不能編譯,是由於參數List<Integer>和List<String>編譯以後都被擦除了,變成了同樣的原生類型List,擦除動做致使這兩種方法的特徵簽名變得如出一轍。

這是由於參數List<Integer>和List<String>編譯以後都被擦除了,變成了同樣的原生類型List。因此此處傳入的實際上是同一個類型。不是重載。

【後記】 以初學者的心態去重學java,我忽然發現本身不懂的愈來愈多了,問號一個接一個的在腦子裏跳舞。就拿泛型來講,我仍是有不少要去學習和探究的地方,這裏也只是作個學習後的整理,等下一次我有了新的收穫後再繼續分享,但願能夠和你們一塊兒,進步進步再進一步~

積累點滴,作好本身~

相關文章
相關標籤/搜索