1 爲何使用泛型java
爲何使用泛型?首先,咱們必須知道什麼是泛型。泛型,簡單來講,就是將類型參數化,即用一個參數來表明類型。比方說,在學習泛型以前,咱們定義的變量都是指明瞭具體類型的,如String str定義了字符串類型的str, Integre num;定義了整型的num等(包括Object類型)。那麼學習了泛型以後,咱們就可能會定義相似T a;這種不指明具體類型的變量。a的類型隨着傳入參數的不一樣而變化。數組
比方說Java集合類List<E>就是一個泛型類(準確說是接口),裏面不只能夠裝Integer類型的數據,也能夠裝String類型的數據,以及用戶自定義的對象等等。安全
所以,編寫泛型程序意味着:1.咱們的代碼能夠被不一樣類型的對象所重用;2.比隨意地使用Object變量具備更好的安全性(好比避免java.lang.ClassCastException)和可讀性(消除源代碼中的許多強制類型轉換,全部的強制轉換都是自動和隱式的);3.性能較高,泛型代碼能夠爲java編譯器和虛擬機帶來更多的類型信息,這些信息對java程序作進一步優化提供條件。函數
2 泛型類性能
一個泛型類是具備一個或多個類型變量的類。類型變量使用大寫形式。在Java庫中,T(須要時還能夠用臨近的字面U和S)表示任意類型。在咱們寫泛型類時,用T、U等只是習慣用法,其它的A、B、C等都是能夠的。學習
好比,下面代碼定義了具備一個類型變量T的泛型類。優化
public class Computer<T> {
private T data;
public Computer() {
}
public Computer(T data) {
this.data = data;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
下面代碼定義了具備兩個類型變量T和U的泛型類:
public class Computer<T,U> {
private T data;
private U com;
public Computer() {
}
public Computer(T data, U com) {
this.data = data;
this.com = com;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public U getCom() {
return com;
}
public void setCom(U com) {
this.com = com;
}
}
類型變量用<>括起來,放在類名的後面。類型變量能夠有一個或多個,若是有多個時,中間用逗號隔開。
在實際的程序當中,類型參數用在什麼地方呢?泛型類中的類型變量,主要用在三個地方,一是指定方法的返回類型,二是指定域的類型,三是指定局部變量的類型。
指定方法的返回類型:好比前面程序裏的getXxx方法。
指定域的類型:好比前面程序變量的定義T xxx。
指定局部變量的類型:好比前面程序setXxx和getXxx方法裏的參數。
用具體的類型替換類型變量就能夠實例化泛型類型。
咱們仍用上面的Computer類來講明。
Computer<String> com = new Computer<String>();
Computer<String ,String> computer = new Computer<String ,String>("abc","def");
或Computer<String ,String> computer = new Computer("asd","bcv"); //後面構造函數的<String ,String>能夠不寫。
3 泛型方法
泛型方法時一個帶有類型參數的簡單方法。泛型方法能夠定義在普通類中,也能夠定義在泛型類中。類型變量放在修飾符的後面,返回類型的前面。
下面這個例子展現瞭如何在普通類中定義泛型方法:
public class Phone {
//定義泛型方法
public static <T> String fun(T t){
return t.toString();
}
public static void main(String[] args) {
//當調用一個泛型方法時,在方法名前的<>中放入具體的類型
String str1 = Phone.<Integer>fun(8);
System.out.println(str1);
//實際上大多數狀況下,也是編譯器推薦的方式,方法名前的<>是省略的,以下方式
String str2 = Phone.fun("hello");
System.out.println(str2);
}
}
4 類型變量的限定
像上面這樣,咱們會寫一個泛型類、泛型方法,以及調用使用他們,基本在平常的開發中就沒有什麼問題了。固然,若是想要了解泛型的更多東西,還能夠繼續往下看。
這裏,咱們說一下類型變量的限定。所謂類型變量的限定,就是給類型變量加上約束,好比類型參數必須實現某個接口,繼承某個類等,而不是讓類型參數能夠任意取值。
咱們一泛型方法爲例,好比:
定義一個Father類
public class Father {
public void hunt(){
System.out.println("I'm good at hunting");
}
}
定義一個兒子類:
public class Son {
public static <T> void fun(T t){
//下面這句編譯器會報錯
t.hunt();
}
}
如上,在Father類中,有一個hunt()方法。在Son類裏,有一個泛型方法fun,並調用了hunt()方法。程序顯然是錯誤的,由於咱們不知道T是什麼類型,它有沒有hunt()方法,直接t.hunt()確定不行,並且編譯器也會直接給咱們報錯。
但是,若是咱們必需要讓t具備hunt()函數,即函數fun(T t);必須以t。hunt()的方式調用hunt函數,那該怎麼辦呢?很簡單,由於hunt()方法在Father類中定義,咱們只要讓傳入的參數t是Father類型或其子類類型就能夠了,也就是,咱們傳入的這個參數t,不能再是任意類型,必須讓它繼承Father類。
因此,上述代碼改爲下面這樣:
public class Son {
//<T>改成<T extends Father>
public static <T extends Father> void fun(T t){
t.hunt();
}
}
這樣,咱們就能夠知道,若是你想調用Son裏的fun(T t);函數,那麼T必須extends Father,即傳入的參數必須的Father及其子類型,否則無法調用。這樣咱們就不用擔憂t.hunt()的調用會出錯了。
一個類型變量能夠有多個限定,例如:
T必須同時繼承(或實現)A、B 、C三個類(或接口):<T extends A & B & C>
T必須同時繼承(或實現)A、B 、C三個類(或接口),U必須同時繼承(或實現)D、E兩個類(或接口):<T extends A & B & C, U extends D & E>
限定類型用 & 分隔,類型變量用逗號分隔。
須要注意的是:
(1)不管的類型變量須要繼承某個父類仍是實現某個接口,通通用關鍵字extends,而不能用implements。
(2)類型參數能夠指定多個限定接口,但只能指定一個限定類,若是有限定類,限定類必須放在限定列表的第一個。比方說,Father和Animal類是咱們自定義的兩個類,Comparable和List是Java自帶的接口。
<T extends Father & List & Comparable>是能夠的,徹底沒問題,
<T extends Father & Animal>是不能夠的,由於限定類只能有一個,不能是Father和Animal兩個,
<T extends List & Comparable & Father>是不能夠的,由於List和Comparable是接口,Father是類,類必須放在限定列表的第一個。
另外,
<? extends T>表示包括T在內的任何T的子類,屬於子類型限定。
<? super T>表示包括T在內的任何T的父類,屬於超類型限定。
5 擦除
參考文章 http://blog.csdn.net/lonelyroamer/article/details/7868820
使用泛型的時候加上的類型參數,編譯器在編譯時會去掉,在生成的Java字節碼中是不包含泛型中的類型信息的。這個過程就稱爲類型擦除。
所以,事實上,虛擬機並不知道泛型,不管是Computer<String>,仍是Computer<Integer>,在JVM的眼裏,通通是Computer。全部的泛型在編譯階段就已經被處理成了普通類和方法,即咱們所說的原始類型。原始類型的名字就是刪去類型參數後的泛型類型名。擦除類型變量,並替換爲限定類型(無限定類型的變量用Object)。
好比咱們寫的第一個泛型類Computer<T>的原始類型以下:
public class Computer {
//由於T是一個無限定的類型變量,因此直接替換爲Object
private Object data;
public Computer() {
}
public Computer(Object data) {
this.data = data;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
結果就是普通的類,和引入泛型以前,咱們使用Object同樣。
類型變量若是沒有限定,就用Object替換,若是有限定類型,就替換爲限定類型,若是限定類型有多個,就替換爲第一個限定類型。
其實,咱們還能夠手動驗證一下類型擦除。
爲了方便,我直接使用Java自帶的泛型類List,代碼以下:
public static void main(String[] args) {
ArrayList<String> stringList = new ArrayList<String>();
ArrayList<Integer> integerList = new ArrayList<Integer>();
System.out.println(stringList.getClass()==integerList.getClass());
}
運行這個main方法,控制檯會打印true。這說明,JVM在運行的時候,類型變量已經擦除了,它所知道的只有List。
咱們還能夠以另外一種方式驗證:
public static void main(String[] args) {
ArrayList<Integer> arrayList=new ArrayList<Integer>();
arrayList.add(1);//這樣調用add方法只能存儲整形,由於泛型類型的實例爲Integer
try {
//經過反射獲取類的運行時信息能夠存儲字符串
arrayList.getClass().getMethod("add", Object.class).invoke(arrayList, "Hello!");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
System.out.println(arrayList);
}
運行這個main函數,控制檯會打印:[1, Hello!]簡直震驚!咱們居然在一個整型數組列表ArrayList<Integer>裏存進了一個字符串"Hello!"。不用驚訝,事實本該如此。由於類型擦除,類型變量被替換爲Object,字符串"Hello!"天然能夠存進去。