【泛型】Generic
阿新 • • 發佈:2017-09-04
cas 不同 源碼 膨脹 關於 比較 pro -o 微軟雅黑 參考:http://blog.csdn.net/lonelyroamer/article/details/7864531#commentshttp://blog.csdn.net/lonelyroamer/article/details/7868820#comments http://blog.csdn.net/LonelyRoamer/article/details/7927212#comments
簡單來說,泛型是JDK1.5中出現的安全機制。
好處:將運行時期的ClassCastException問題轉到了編譯時期,避免了強制轉換的麻煩。
什麽時候用:當操作的引用數據類型不確定的時候,就使用<>,將要操作的引用數據類型傳入即可。其實<>就是一個用於接收具體引用數據類型的參數範圍。在程序中,只要用到了帶有<>的類或者接口,就要明確傳入的具體引用數據類型。
泛型技術是給編譯器使用的技術,用於編譯時期。確保了類型的安全。
運行時,會將泛型去掉,生成的class文件中是不帶泛型的,這個稱為泛型的擦除。
為什麽擦除呢?因為為了兼容運行的類加載器。
泛型的補償:在運行時,通過獲取元素的類型進行轉換動作。這樣就不用再手動強制轉換了。
泛型的通配符【?】未知類型。
泛型的限定:
利用泛型,我們可以:
泛型的基本概念
泛型的定義:泛型是JDK 1.5的一項新特性,它的本質是參數化類型 ParameterizedType,即帶有類型參數的類型。也就是說所操作的數據類型被指定為一個參數,在用到的時候再指定具體的類型。如:List<T>、Map<Integer, String>、List<? extends Number>。
GenericDeclaration接口是聲明類型變量的所有實體的公共接口,也就是說,只有實現了該接口才能在對應的實體上聲明類型變量。這些實體目前只有三個:Class、Construstor、Method。當這種參數化類型用在類、接口和方法的創建中時,分別稱為泛型類、泛型接口和泛型方法。
註意:因為直接實現子類沒有Field類,所以在屬性上面不能定義類型變量。
泛型思想早在C++語言的模板(Templates)中就開始生根發芽,在Java語言處於還沒有出現泛型的版本時,只能通過 "Object是所有類型的父類" 和 "類型強制轉換" 兩個特點的配合來實現類型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一個Object對象,由於Java語言裏面所有的類型都繼承於java.lang.Object,那Object轉型為任何對象成都是有可能的。但是也因為有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object到底是個什麽類型的對象。在編譯期間,編譯器無法檢查這個Object的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多ClassCastException的風險就會被轉嫁到程序運行期之中。
泛型技術在C#和Java之中的使用方式看似相同,但實現上卻有著根本性的分歧,C#裏面泛型無論在程序源碼中、編譯後的IL中(Intermediate Language,中間語言,這時候泛型是一個占位符)或是運行期的CLR中都是切實存在的,比如 List<int> 與 List<String> 就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現稱為類型膨脹,基於這種方法實現的泛型被稱為真實泛型。
Java語言中的泛型則不一樣,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經被替換為原來的原始類型(Raw Type,也稱為裸類型)了,並且在相應的地方插入了強制轉型代碼,因此對於運行期的Java語言來說,ArrayList<int> 與 ArrayList<String> 就是同一個類型。所以說泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為類型擦除,基於這種方法實現的泛型被稱為偽泛型。
使用泛型機制編寫的程序代碼要比那些雜亂的使用Object變量,然後再進行強制類型轉換的代碼具有更好的安全性和可讀性。泛型對於集合類來說尤其有用。
泛型程序設計(Generic Programming)意味著編寫的代碼可以被很多不同類型的對象所重用。
實例分析
在JDK1.5之前,Java泛型程序設計是用繼承來實現的。因為Object類是所用類的基類,所以只需要維持一個Object類型的引用即可。就比如ArrayList只維護一個Object引用的數組:
所以。在JDK1.5之後,加入了泛型來解決類似的問題。例如在ArrayList中使用泛型:
還要明白的是,泛型特性是向前兼容的。盡管 JDK 5.0 的標準類庫中的許多類,比如集合框架,都已經泛型化了,但是使用集合類的現有代碼(沒有加泛型的代碼)可以繼續不加修改地在 JDK 1.5 中工作。
泛型的使用
泛型的參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口和泛型方法。下面看看具體是如何定義的。
泛型類:類名後面
泛型類就是在聲明類時,定義了一個或多個類型變量的類。泛型類中定義的類型變量的作用範圍為當前泛型類中。泛型類中定義的類型變量用於,在多個方法簽名間實施類型約束。例如,當創建一個 Map<K, V> 類型的對象時,您就在方法之間宣稱一個類型約束,您 put() 的值將與 get() 返回的值的類型相同。
定義一個泛型類十分簡單,只需要在類名後面加上<>,再在裏面加上類型參數:
泛型接口
泛型接口和泛型類差不多:
泛型方法:返回值之前
泛型方法就是在聲明方法時,定義了一個或多個類型變量的方法。
泛型方法中定義的類型變量的作用範圍為當前泛型方法中。
泛型方法中定義的類型變量用於,在該方法的多個參數之間,或在該方法的參數與返回值之間,宣稱一個類型約束。
泛型變量的類型限定
對於上面定義的泛型變量,因為在編譯之前,也就是我們還在定義這個泛型方法的時候,我們並不知道這個泛型類型 T 到底是什麽類型,所以,只能默認T為原始類型Object,所以它只能調用來自於Object的那幾個方法。如果我們想限定類型的範圍,比如必須是某個類的子類,或者某個接口的實現類,這時可以使用類型限定對類型變量T設置限定(bound)來實現。
類型限定在泛型類、泛型接口和泛型方法中都可以使用,不過要註意下面幾點:
通配符?的使用
通配符有三種:
1、泛型中的?通配符
如果定義一個方法,該方法用於打印出任意參數化類型的集合中的所有數據,如果這樣寫
2、?通配符的擴展:界定通配符的上邊界
List<? extends S> x = new ArrayList<T>();
類型S指定一個數據類型,那麽類型T就只能是類型S或者是類型S的子類
3、?通配符的擴展:界定通配符的下邊界
List<? super S> x = new ArrayList<T>();
類型S指定一個數據類型,那麽類型T就只能是類型S或者是類型S的父類
類型擦除
前面已經說了,Java的泛型是偽泛型。為什麽說Java的泛型是偽泛型呢?因為,在編譯期間,所有的泛型信息都會被擦除掉。正確理解泛型概念的首要前提是理解類型擦出(type erasure)。
Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱為類型擦除。
如在代碼中定義的List<object>和List<String>等類型,在編譯後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發現可能出錯的地方,但是仍然無法避免在運行時刻出現類型轉換異常的情況。類型擦除也是Java的泛型實現方法與C++模版機制實現方式之間的重要區別。
可以通過兩個簡單的例子,來證明java泛型的類型擦除。案例一:
案例二:
類型擦除後保留的原始類型
在上面,兩次提到了原始類型,什麽是原始類型?原始類型(raw type)就是擦除去了泛型信息,最後在字節碼中的類型變量的真正類型。無論何時定義一個泛型類型,相應的原始類型都會被自動地提供。類型變量被擦除(crased),並使用其限定類型(無限定的變量用Object)替換。
例如:
如果類型變量有限定,那麽原始類型就用第一個邊界的類型變量來替換。
比如Pair這樣聲明:
如果Pair這樣聲明
要區分原始類型和泛型變量的類型
在調用泛型方法的時候,可以指定泛型,也可以不指定泛型。
附加:GenericDeclaration 接口
聲明類型變量的所有實體的公共接口。
可以聲明類型變量的實體的公共接口,也就是說,只有實現了該接口才能在對應的實體上聲明(定義)類型變量,這些實體目前只有三個:Class、Construstor、Method。註意:因為直接實現子類沒有Field類,所以屬性上面不能定義類型變量。
方法
來自為知筆記(Wiz)
關於泛型的一些重要知識點
泛型由來:早期Java版本(1.4及之前)如果要代指某個泛化類對象,只能使用Object,這樣寫出來的代碼需要增加強轉,而且缺少類型檢查,代碼缺少健壯性。在1.5之後,Java引入了泛型的概念,提供了一套抽象的類型表示方法。簡單來說,泛型是JDK1.5中出現的安全機制。
好處:將運行時期的ClassCastException問題轉到了編譯時期,避免了強制轉換的麻煩。
什麽時候用:當操作的引用數據類型不確定的時候,就使用<>,將要操作的引用數據類型傳入即可。其實<>就是一個用於接收具體引用數據類型的參數範圍。在程序中,只要用到了帶有<>的類或者接口,就要明確傳入的具體引用數據類型。
泛型技術是給編譯器使用的技術,用於編譯時期。確保了類型的安全。
運行時,會將泛型去掉,生成的class文件中是不帶泛型的,這個稱為泛型的擦除。
為什麽擦除呢?因為為了兼容運行的類加載器。
泛型的補償:在運行時,通過獲取元素的類型進行轉換動作。這樣就不用再手動強制轉換了。
泛型的通配符【?】未知類型。
泛型的限定:
- 【? extends E】接收E類型或者E的子類型對象。上限。一般存儲對象的時候用。比如 添加元素 addAll。
- 【? super E】接收E類型或者E的父類型對象。下限。一般取出對象的時候用。比如比較器。
利用泛型,我們可以:
- 1、表示多個可變類型之間的相互關系:HashMap<T,S>表示類型T與S的映射,HashMap<T, S extends T>表示T的子類與T的映射關系。
- 2、細化類的能力:ArrayList<T> 可以容納任何指定類型T的數據,當T代指人,則是人的有序列表,當T代指杯子,則是杯子的有序列表,所有對象個體可以共用相同的操作行為。
- 3、復雜類型被細分成更多類型:List<People>和List<Cup>是兩種不同的類型,這意味著List<People> listP = new ArrayList<Cup>()是不可編譯的。這種檢查基於編譯時而非運行時,所以說是不可編譯並非不可運行,因為運行時ArrayList不保留Cup信息。另外要註意,即使People繼承自Object,List<Object> listO = new ArrayList<People>()也是不可編譯的,應理解為兩種不同類型。因為listO可以容納任意類型,而實例化的People列表只能接收People實例,這會破壞數據類型完整性。
泛型的基本概念
泛型的定義:泛型是JDK 1.5的一項新特性,它的本質是參數化類型 ParameterizedType,即帶有類型參數的類型。也就是說所操作的數據類型被指定為一個參數,在用到的時候再指定具體的類型。如:List<T>、Map<Integer, String>、List<? extends Number>。public interface java.lang.reflect.ParameterizedType extends Type
GenericDeclaration接口是聲明類型變量的所有實體的公共接口,也就是說,只有實現了該接口才能在對應的實體上聲明類型變量。這些實體目前只有三個:Class、Construstor、Method。當這種參數化類型用在類、接口和方法的創建中時,分別稱為泛型類、泛型接口和泛型方法。
註意:因為直接實現子類沒有Field類,所以在屬性上面不能定義類型變量。
public interface java.lang.reflect.GenericDeclaration
所有已知實現類:Class、Constructor、Method
泛型思想早在C++語言的模板(Templates)中就開始生根發芽,在Java語言處於還沒有出現泛型的版本時,只能通過 "Object是所有類型的父類" 和 "類型強制轉換" 兩個特點的配合來實現類型泛化。例如在哈希表的存取中,JDK 1.5之前使用HashMap的get()方法,返回值就是一個Object對象,由於Java語言裏面所有的類型都繼承於java.lang.Object,那Object轉型為任何對象成都是有可能的。但是也因為有無限的可能性,就只有程序員和運行期的虛擬機才知道這個Object到底是個什麽類型的對象。在編譯期間,編譯器無法檢查這個Object的強制轉型是否成功,如果僅僅依賴程序員去保障這項操作的正確性,許多ClassCastException的風險就會被轉嫁到程序運行期之中。
泛型技術在C#和Java之中的使用方式看似相同,但實現上卻有著根本性的分歧,C#裏面泛型無論在程序源碼中、編譯後的IL中(Intermediate Language,中間語言,這時候泛型是一個占位符)或是運行期的CLR中都是切實存在的,比如 List<int> 與 List<String> 就是兩個不同的類型,它們在系統運行期生成,有自己的虛方法表和類型數據,這種實現稱為類型膨脹,基於這種方法實現的泛型被稱為真實泛型。
Java語言中的泛型則不一樣,它只在程序源碼中存在,在編譯後的字節碼文件中,就已經被替換為原來的原始類型(Raw Type,也稱為裸類型)了,並且在相應的地方插入了強制轉型代碼,因此對於運行期的Java語言來說,ArrayList<int> 與 ArrayList<String> 就是同一個類型。所以說泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為類型擦除,基於這種方法實現的泛型被稱為偽泛型。
使用泛型機制編寫的程序代碼要比那些雜亂的使用Object變量,然後再進行強制類型轉換的代碼具有更好的安全性和可讀性。泛型對於集合類來說尤其有用。
泛型程序設計(Generic Programming)意味著編寫的代碼可以被很多不同類型的對象所重用。
實例分析
在JDK1.5之前,Java泛型程序設計是用繼承來實現的。因為Object類是所用類的基類,所以只需要維持一個Object類型的引用即可。就比如ArrayList只維護一個Object引用的數組:public class ArrayList{
public Object get(int i){......}
public void add(Object o){......}
......
private Object[] elementData;
}
這樣會有兩個問題:- 沒有錯誤檢查,可以向數組列表中添加任何類的對象
- 在取元素的時候,需要進行強制類型轉換
/**jdk1.5之前的寫法,容易出問題*/
ArrayList arrayList1=new ArrayList();
arrayList1.add(1);
arrayList1.add(1L);
arrayList1.add("asa");
int i=(Integer) arrayList1.get(1);//因為不知道取出來的值的類型,類型轉換的時候容易出錯
這裏的第二個元素是一個長整型,而你以為是整形,所以在強轉的時候發生了錯誤。所以。在JDK1.5之後,加入了泛型來解決類似的問題。例如在ArrayList中使用泛型:
/** jdk1.5之後加入泛型*/
ArrayList<String> arrayList2=new ArrayList<String>(); //限定數組列表中的類型
//arrayList2.add(1); //因為限定了類型,所以不能添加整形
//arrayList2.add(1L);//因為限定了類型,所以不能添加整長形
arrayList2.add("asa");//只能添加字符串
String str=arrayList2.get(0);//因為知道取出來的值的類型,所以不需要進行強制類型轉換
還要明白的是,泛型特性是向前兼容的。盡管 JDK 5.0 的標準類庫中的許多類,比如集合框架,都已經泛型化了,但是使用集合類的現有代碼(沒有加泛型的代碼)可以繼續不加修改地在 JDK 1.5 中工作。
泛型的使用
泛型的參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口和泛型方法。下面看看具體是如何定義的。泛型類:類名後面
泛型類就是在聲明類時,定義了一個或多個類型變量的類。泛型類中定義的類型變量的作用範圍為當前泛型類中。泛型類中定義的類型變量用於,在多個方法簽名間實施類型約束。例如,當創建一個 Map<K, V> 類型的對象時,您就在方法之間宣稱一個類型約束,您 put() 的值將與 get() 返回的值的類型相同。public class HashMap<K,V> {
public V put(K key, V value) {...}
public V get(Object key) {...}
...
}
定義一個泛型類十分簡單,只需要在類名後面加上<>,再在裏面加上類型參數:
public class Pair<T> {
private T value;
public Pair(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
現在我們就可以使用這個泛型類了:public static void main(String[] args) throws ClassNotFoundException {
Pair<String> pair = new Pair<String>("Hello");//註意,"="號左邊和右邊都要使用<>指定泛型的實際類型
String str = pair.getValue();
pair.setValue("World");
}
泛型類可以有多個類型變量,例如:class Pair<T, S, P, U, E> { }
註意:類型變量使用大寫形式,且比較短,這是很常見的。在Java庫中,使用變量E表示集合的元素類型,K和V分別表示關鍵字與值的類型。需要時還可以用臨近的字母U和S表示“任意類型”。泛型接口
泛型接口和泛型類差不多:interface Show<T,U>{
void show(T t,U u);
}
實現類public class ShowTest implements Show<String, Date> {
@Override
public void show(String t, Date u) {
System.out.println(t + " " + u.getTime());
}
}
測試一下:Show<String, Date> show = new ShowTest();
show.show("包青天", new Date());
泛型方法:返回值之前
泛型方法就是在聲明方法時,定義了一個或多個類型變量的方法。泛型方法中定義的類型變量的作用範圍為當前泛型方法中。
泛型方法中定義的類型變量用於,在該方法的多個參數之間,或在該方法的參數與返回值之間,宣稱一個類型約束。
class Person<S> {
public <W> void show(W w) {//這裏的【W】完全等價於Object
if (w != null) System.out.println(w.toString());
}
public static <Y> void staticShow(Y y) {
if (y != null) System.out.println(y.toString());
//靜態方法不能訪問在類聲明上定義的類型變量
//S s;//錯誤提示:Cannot make a static reference to the non-static type S
}
}
泛型變量的類型限定
對於上面定義的泛型變量,因為在編譯之前,也就是我們還在定義這個泛型方法的時候,我們並不知道這個泛型類型 T 到底是什麽類型,所以,只能默認T為原始類型Object,所以它只能調用來自於Object的那幾個方法。如果我們想限定類型的範圍,比如必須是某個類的子類,或者某個接口的實現類,這時可以使用類型限定對類型變量T設置限定(bound)來實現。類型限定在泛型類、泛型接口和泛型方法中都可以使用,不過要註意下面幾點:
- 無限定的泛型變量等價於Object(白哥添加)
- 不管該限定是類還是接口,統一都使用關鍵字 extends
- 可以使用 & 符號給出多個限定
- 如果限定既有接口也有類,那麽類必須只有一個,並且放在首位置
public static <T extends Comparable> T get(T t1,T t2) //繼承或實現都用extends
public static <T extends Comparable & Serializable> T get(T t1,T t2) //使用 & 符號給出多個限定
public static <T extends Object & Comparable & Serializable> T get(T t1,T t2) //繼承的類Object必須放在首位
通配符?的使用
通配符有三種:- 無限定通配符 形式<?>
- 上邊界限定通配符 形式< ? extends Number>
- 下邊界限定通配符 形式< ? super Number>
1、泛型中的?通配符
如果定義一個方法,該方法用於打印出任意參數化類型的集合中的所有數據,如果這樣寫
public static void main(String[] args) throws Exception {
List<Integer> listInteger = new ArrayList<Integer>();
printCollection(listInteger);//報錯 The method printCollection(Collection<Object>) in the type Test is not applicable for the arguments (List<Integer>)
}
public static void printCollection(Collection<Object> collection) {
for (Object obj : collection) {
System.out.println(obj);
}
}
語句printCollection(listInteger);報錯,這是因為泛型的參數是不考慮繼承關系的,就直接報錯。這就得用?通配符public static void printCollection(Collection<?> collection) {...}
在方法 printCollection 中不能出現與參數類型有關的方法,比如:collection.add(new Object());//The method add(capture#1-of ?) in the type Collection<capture#1-of ?> is not applicable for the arguments (Object)
因為程序調用這個方法的時候傳入的參數不知道是什麽類型的。但是可以調用與參數類型無關的方法比如 collection.size();總結:使用?通配符可以引用其他各種參數化的類型,?通配符定義的變量的主要用作引用,可以調用與參數化無關的方法,不能調用與參數化有關的方法。2、?通配符的擴展:界定通配符的上邊界
List<? extends S> x = new ArrayList<T>();
類型S指定一個數據類型,那麽類型T就只能是類型S或者是類型S的子類
List<? extends Number> x = new ArrayList<Integer>();//正確
List<? extends Number> y = new ArrayList<Object>();//錯誤 Type mismatch: cannot convert from ArrayList<Object> to List<? extends Number>
3、?通配符的擴展:界定通配符的下邊界
List<? super S> x = new ArrayList<T>();
類型S指定一個數據類型,那麽類型T就只能是類型S或者是類型S的父類
List<? super Number> y = new ArrayList<Object>();//正確
List<? super Number> x = new ArrayList<Integer>();//錯誤 Type mismatch: cannot convert from ArrayList<Integer> to List<? super Number>
提示:限定通配符總是包括自己類型擦除
前面已經說了,Java的泛型是偽泛型。為什麽說Java的泛型是偽泛型呢?因為,在編譯期間,所有的泛型信息都會被擦除掉。正確理解泛型概念的首要前提是理解類型擦出(type erasure)。Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會在編譯器在編譯的時候去掉。這個過程就稱為類型擦除。
如在代碼中定義的List<object>和List<String>等類型,在編譯後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。Java編譯器會在編譯時盡可能的發現可能出錯的地方,但是仍然無法避免在運行時刻出現類型轉換異常的情況。類型擦除也是Java的泛型實現方法與C++模版機制實現方式之間的重要區別。
可以通過兩個簡單的例子,來證明java泛型的類型擦除。案例一:
ArrayList<String> list1 = new ArrayList<String>();
ArrayList<Integer> list2 = new ArrayList<Integer>();
System.out.println((list1.getClass() == list2.getClass()) + " " + (list1.getClass() == ArrayList.class));//true true
在這個例子中,我們定義了兩個ArrayList集合,不過一個是ArrayList<String>泛型類型,只能存儲字符串。一個是ArrayList<Integer>泛型類型,只能存儲整形。最後,我們通過兩個ArrayList對象的getClass方法獲取它們的類的信息,最後發現兩者相等,且等於ArrayList.class。說明泛型類型String和Integer都被擦除掉了,只剩下了原始類型。案例二:
List<Integer> list = new ArrayList<Integer>();
list.add(10086);
Method method = list.getClass().getMethod("add", Object.class);
//運行時利用反射機制調用集合的add方法,跳過編譯時的泛型檢查
method.invoke(list, "雖然集合中對元素限定的泛型是Integer,但是也能通過反射把字符串添加到集合中");
Object object = list.get(1);
System.out.println(object.getClass().getSimpleName() + " " + (object.getClass() == String.class));//String true
try {
System.out.println(((Object) list.get(1)).getClass());//class java.lang.String
System.out.println(list.get(1).getClass());//如果不指定list.get(1)的類型,則會默認將其強制轉換為集合上指定的泛型類型
} catch (Exception e) {
e.printStackTrace();//java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
}
因為泛型只在編譯的時候起作用,在運行的時候,你得ArrayList已經不受泛型的控制了,也就是說跟已經沒有泛型限定的ArrayList沒有任何區別了。而反射直接獲得了add方法的字節碼,跳過編譯層在運行時直接添加,這樣就騙過了編譯。類型擦除後保留的原始類型
在上面,兩次提到了原始類型,什麽是原始類型?原始類型(raw type)就是擦除去了泛型信息,最後在字節碼中的類型變量的真正類型。無論何時定義一個泛型類型,相應的原始類型都會被自動地提供。類型變量被擦除(crased),並使用其限定類型(無限定的變量用Object)替換。例如:
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
Pair<T>的原始類型為:class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
因為在Pair<T>中,T是一個無限定的類型變量,所以用Object替換。其結果就是一個普通的類,如同泛型加入java編程語言之前已經實現的那樣。在程序中可以包含不同類型的Pair,如Pair<String>或Pair<Integer>,但是,擦除類型後它們就成為原始的Pair類型了,原始類型都是Object。從上面的那個例2中,我們也可以明白ArrayList<Integer>被擦除類型後,原始類型也變成了Object,所以通過反射我們就可以存儲字符串了。如果類型變量有限定,那麽原始類型就用第一個邊界的類型變量來替換。
比如Pair這樣聲明:
public class Pair<T extends Comparable& Serializable> { ... }
那麽原始類型就是Comparable如果Pair這樣聲明
public class Pair<T extends Serializable & Comparable>
那麽原始類型就用Serializable替換,而編譯器在必要的時要向 Comparable 插入強制類型轉換。為了提高效率,應該將標簽接口(即沒有方法的接口)放在邊界限定列表的末尾。要區分原始類型和泛型變量的類型
在調用泛型方法的時候,可以指定泛型,也可以不指定泛型。
- 在不指定泛型的時候,泛型變量的類型為 該方法中的幾種類型的同一個父類的最小級,直到Object。
- 在指定泛型的時候,該方法中的幾種類型必須是該泛型實例類型或者其子類。
public class Test {
public static void main(String[] args) {
/**不指定泛型的時候,泛型變量的類型為 該方法中的幾種類型的同一個父類的最小級,直到Object*/
int i = Test.add(1, 2); //這兩個參數都是Integer,所以T為Integer類型
Number f = Test.add(1, 1.2);//這兩個參數一個是Integer,一個是Float,所以取同一父類的最小級,為Number
Object o = Test.add(1, "asd");//這兩個參數一個是Integer,一個是Float,所以取同一父類的最小級,為Object
/**指定泛型的時候,該方法中的幾種類型必須是該泛型實例類型或者其子類*/
int a = Test.<Integer> add(1, 2);//指定了Integer,所以只能為Integer類型或者其子類
//int b = Test.<Integer> add(1, 2.2);//編譯錯誤,指定了Integer,不能為Float
Number c = Test.<Number> add(1, 2.2); //指定為Number,所以可以為Integer和Float
}
public static <T> T add(T x, T y) {
return y;
}
}
其實在泛型類中,不指定泛型的時候也差不多,只不過這個時候的泛型類型為Object,就比如ArrayList中,如果不指定泛型,那麽這個ArrayList中可以放任意類型的對象。附加:GenericDeclaration 接口
public interface java.lang.reflect.GenericDeclaration
所有已知實現類:Class、Constructor、Method聲明類型變量的所有實體的公共接口。
可以聲明類型變量的實體的公共接口,也就是說,只有實現了該接口才能在對應的實體上聲明(定義)類型變量,這些實體目前只有三個:Class、Construstor、Method。註意:因為直接實現子類沒有Field類,所以屬性上面不能定義類型變量。
方法
- TypeVariable<?>[] getTypeParameters() 返回聲明順序的 TypeVariable 對象的數組,這些對象表示由此 GenericDeclaration 對象表示的一般聲明聲明的類型變量。
- 返回:表示由此一般聲明聲明的類型變量的 TypeVariable 對象的數組
- 如果底層的一般聲明未聲明任何類型變量,則返回一個 0 長度的數組。
public static <T extends Person, U> void main(String[] args) throws Exception {
Method method = Test.class.getMethod("main", String[].class);
TypeVariable<?>[] tvs = method.getTypeParameters();//返回聲明順序的 TypeVariable 對象的數組
System.out.println("聲明的類型變量有:" + Arrays.toString(tvs));//[T, U]
for (int i = 0; i < tvs.length; i++) {
GenericDeclaration gd = tvs[i].getGenericDeclaration();
System.out.println("【GenericDeclaration】" + gd);//public static void com.bqt.Test.main(java.lang.String[]) throws java.lang.Exception
System.out.println(gd.getTypeParameters()[i] == tvs[i]);//true。 GenericDeclaration和TypeVariable兩者相互持有對方的引用
System.out.println(tvs[i] + " " + tvs[i].getName() + " " + Arrays.toString(tvs[i].getBounds()));//T T [class com.bqt.Person] 和 U U [class java.lang.Object]
}
}
2017-9-4來自為知筆記(Wiz)
【泛型】Generic