java基礎——泛型

原文鏈接:https://blog.csdn.net/stypace/article/details/42102567

版權聲明:本文爲CSDN博主「stypace」的原創文章
原文鏈接:https://blog.csdn.net/stypace/article/details/42102567

1、泛型基本概念
1.1、由來

泛型是JDK 1.5的一項新特性,在Java語言處於還沒有出現泛型的版本時,只能通過Object是所有類型的父類和類型強制轉換兩個特點的配合來實現類型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一個Object對象,由於Java語言裏面所有的類型都繼承於java.lang.Object,那Object轉型爲任何對象成都是有可能的。但是也因爲有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object到底是個什麼類型的對象。在編譯期間,編譯器無法檢查這個Object的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多ClassCastException的風險就會被轉嫁到程序運行期之中。
1.2、僞泛型

泛型技術在C#和Java之中的使用方式看似相同,但實現上卻有着根本性的分歧,C#裏面泛型無論在程序源碼中、編譯後的IL中(Intermediate Language,中間語言,這時候泛型是一個佔位符)或是運行期的CLR中都是切實存在的,List<int>與List<String>就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現稱爲類型膨脹,基於這種方法實現的泛型被稱爲真實泛型。

  Java語言中的泛型則不一樣,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經被替換爲原來的原始類型(Raw Type,也稱爲裸類型)了,並且在相應的地方插入了強制轉型代碼,因此對於運行期的Java語言來說,ArrayList<int>與ArrayList<String>就是同一個類。所以說泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱爲類型擦除,基於這種方法實現的泛型被稱爲僞泛型。
1.3、泛型的使用

    泛型類

    泛型接口


    泛型方法

1.4、一些要求及規則

    不能使基本類型
    不管該限定是類還是接口,統一都使用關鍵字extends
    可以使用&符號給出多個限定
     如果限定既有接口也有類,那麼類必須只有一個,並且放在首位置

詳見:http://blog.csdn.net/lonelyroamer/article/details/7864531
2、原理
2.1、類型擦除

Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。

如在代碼中定義的List<object>和List<String>等類型,在編譯後都會編程List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。
2.2、重要:用反射來看泛型的機制(甚至可以破壞)

在程序中定義了一個ArrayList泛型類型實例化爲Integer的對象,如果直接調用add方法,那麼只能存儲整形的數據。不過當我們利用反射調用add方法的時候,卻可以存儲字符串。這說明了Integer泛型實例在編譯之後被擦除了,只保留了原始類型。
2.3、原始類型

原始類型(raw type)就是擦除去了泛型信息,最後在字節碼中的類型變量的真正類型。無論何時定義一個泛型類型,相應的原始類型都會被自動地提供。類型變量被擦除(crased),並使用其限定類型(使用extends的,如果extends多個則原始類型就用第一個邊界的類型變量來替換)替換,無限定的變量用Object替換。
3、類型擦除引起的問題及解決辦法
3.1、先檢查、再編譯

因爲類型擦除是在編譯期完成的,在運行的時候就會忽略泛型,爲了保證在運行的時候不出現類型錯誤,就需要在編譯之前就檢查是否滿足泛型要求(類型檢查)。

3.2、類型檢查的依據

以上兩種情況都沒有錯誤:第一種情況,在使用arrayList1的時候與完全使用泛型參數一樣的效果,因爲new ArrayList()只是在內存中新開闢一個存儲空間,它並不能判斷類型,而真正涉及類型檢查的是它的引用,所以在調用arrayList1的時候會進行類型檢查。同理,第二種情況,就不會進行類型檢查。

3.3、泛型參數化類型沒有繼承關係

    第一種情況,可以擴展爲一下形式:

假設它編譯沒錯,那麼當我們使用arrayList2引用用get()方法取值的時候,返回的都是String類型的對象(類型檢測是根據引用來決定的),可是它裏面實際上已經被我們存放了Object類型的對象,這樣就會出現ClassCastException。

    第二種情況,同樣擴展爲:

雖然不會出現ClassCastException,但是假設它編譯沒錯,那麼當我們使用arrayList2引用用get()方法取值的時候,返回的都是Object類型的對象,需要進行類型轉換才行,這樣,泛型就完全沒有作用了。
4、類型擦除與多態的衝突和解決方法

現在看來我們在子類中重寫了父類的兩個方法,而實際上,經過類型擦除之後:

可以看到,父類和子類的方法中參數類型不同,所以如果是在普通的繼承關係中,這完全不是重寫,而是重載;但是如果在泛型中呢?

如果是重載,那麼子類中兩個setValue方法,一個是參數Object類型,一個是Date類型,可是我們發現,根本就沒有這樣的一個子類繼承自父類的Object類型參數的方法。所以說,在泛型中確實是重寫了,而不是重載。
4.1、橋方法

         通過編譯源代碼會發現DataInter最後會有四種方法,其中兩個是編譯器自己生成的橋方法,它的參數類型是Object,也就是說,子類中真正覆蓋/重寫父類兩個方法的就是這兩個我們看不到的橋方法。而打在我們自己定義的setvalue和getValue方法只不過是假象。而橋方法的內部實現,就只是去調用我們自己重寫的那兩個方法。
5、泛型在靜態類和靜態方法中的問題

泛型類中的靜態方法和靜態變量不可以使用泛型類所聲明的泛型類型參數。

因爲泛型類中的泛型參數的實例化是在定義對象的時候指定的,而靜態變量和靜態方法不需要使用對象來調用。對象都沒有創建,如何確定這個泛型參數是何種類型,所以當然是錯誤的。

因爲這是一個泛型方法,在泛型方法中使用的T是自己在方法中定義的T,而不是泛型類中的T。調用這個方法時會聲明T類型的。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章