看JDK源碼的時(shí)候發(fā)現(xiàn)好多地方都在使用泛型,但是平常項(xiàng)目開發(fā)過程中很少使用到泛型,鑒于自己對(duì)泛型也一知半解,所以趁這個(gè)機(jī)會(huì)系統(tǒng)的學(xué)習(xí)一遍
什么是泛型
泛型即參數(shù)化類型,就是聲明的類型參數(shù)在使用的時(shí)候用具體的類型參數(shù)來替換。
為什么使用泛型
使用泛型的好處:例如,定義一個(gè)泛型類型的方法,可以處理String,Integer類型的參數(shù)而不用針對(duì)每種參數(shù)都實(shí)現(xiàn)一個(gè)方法
總所周知Object類是所有類的超類,那么為什么不定義一個(gè)參數(shù)類型是Object的方法呢,這樣方法也可以處理String,Integer類型的參數(shù)?
參考如下代碼,實(shí)例化一個(gè)Student類的對(duì)象并放入定義為L(zhǎng)ist<Object>的列表中,但是取值的時(shí)候編譯報(bào)錯(cuò),必須通過強(qiáng)類型轉(zhuǎn)換來進(jìn)行賦值

這種強(qiáng)制類型轉(zhuǎn)換是不安全的轉(zhuǎn)換方式,例如,在list中分別放入了String和Integer類型的參數(shù),但是取值的時(shí)候?qū)tring類型的參數(shù)強(qiáng)制轉(zhuǎn)換成Integer類型就會(huì)報(bào)錯(cuò)拋異常。
所以通過Object類型的引用變量引用所有類型的對(duì)象不具有安全性,泛型可以擴(kuò)展了重用代碼的能力,并且可以安全、容易的重用代碼。
泛型類
定義一個(gè)泛型類Gen:
package com.victor.technology.generic;
public class Gen<T>
{
private T ob;
public Gen(T ob)
{
this.ob = ob;
}
public T getob()
{
return this.ob;
}
public void showType()
{
System.out.println("The type of T is: " + ob.getClass().getName());
}
public void showValue()
{
System.out.println("The value of ob is: " + ob);
}
}
定義一個(gè)測(cè)試類:
package com.victor.technology.generic;
public class GenericTest
{
public static void main(String[] args)
{
Gen<Integer> iob = new Gen<Integer>(88);
iob.showType();
iob.showValue();
Gen<String> strob = new Gen<String>("Generic Test");
strob.showType();
strob.showValue();
}
}
執(zhí)行結(jié)果如下:
The type of T is: java.lang.Integer
The value of ob is: 88
The type of T is: java.lang.String
The value of ob is: Generic Test
1.泛型只能使用封裝類型
如下聲明是錯(cuò)誤非法的:
Gen<int> iob = new Gen<int>(88);
2.基于不同類型參數(shù)的泛型參數(shù)是不同的
如下寫法也是錯(cuò)誤的:
iob = strob
雖然iob和strob都是Gen<T>類型,但它們是不同的類型引用,因?yàn)閰?shù)類型不同
3.泛型提升類型安全性
使用泛型可以將運(yùn)行時(shí)錯(cuò)誤轉(zhuǎn)換成編譯時(shí)錯(cuò)誤,這是泛型的主要優(yōu)勢(shì)
有界類型
可以再具體的限定一下泛型T的類型,主要有兩種形式:
1.T extends MyClass
例如 Class Gen<T extends MyClass & MyInterface>{// ...
通過類MyClass和接口MyInterface對(duì)T進(jìn)行了限制,因此,所有傳遞給T的類型參數(shù)都必須繼承MyClass類和實(shí)現(xiàn)MyInterface接口
2.T super MyCalss
例如 Class Gen<T super MyClass>{// ...
所有傳遞給T的類型參數(shù)都必須屬于MyClass類的超類
使用通配符參數(shù)
在泛型中,?表示通配符,代表位置類型。<? extends Object>表示上邊界限定通配符,<? super Object>表示下邊界限定通配符。
?與T的區(qū)別:
T:作用在模板上,用于將數(shù)據(jù)類型進(jìn)行參數(shù)化,不能用于實(shí)例化對(duì)象
?:不能用在模板上,在實(shí)例化對(duì)象的時(shí)候,不確定泛型參數(shù)的具體類型時(shí),可以使用通配符進(jìn)行對(duì)象定義
例如,實(shí)例化泛型對(duì)象,我們不能夠確定eList存儲(chǔ)的數(shù)據(jù)類型是Integer還是Long,因此我們使用List<? extends Number>定義變量的類型:
List<? extends Number> list = null;
list = new ArrayList<Integer>();
list = new ArrayList<Long>();
泛型方法
泛型類,是在實(shí)例化類的時(shí)候指明泛型的具體類型;泛型方法,是在調(diào)用方法的時(shí)候指明泛型的具體類型 。
泛型方法的定義:
public <T> T getValue(T ob)
{
...
}
其中<T>是方法的泛型參數(shù)聲明
1.泛型方法的基本用法
定義一個(gè)泛型方法getValue()并使用:
package com.victor.technology.generic;
public class GenericTest
{
public static void main(String[] args)
{
GenericTest gen = new GenericTest();
System.out.println(gen.getValue(5));
System.out.println(gen.getValue("Generic test"));
}
public <T> T getValue(T t)
{
return t;
}
}
執(zhí)行結(jié)果:
5
Generic test
2.類中的泛型方法
定義如下泛型類:
package com.victor.technology.generic;
public class Gen<T>
{
private T ob;
public Gen(T ob)
{
this.ob = ob;
}
public T getob()
{
return this.ob;
}
public <E> void getValue(E t)
{
//...
}
public <T> void showResult(T t)
{
//...
}
}
getValue:在泛型類中聲明了一個(gè)泛型方法,使用泛型E,這種泛型E可以為任意類型??梢灶愋团cT相同,也可以不同。由于泛型方法在聲明的時(shí)候會(huì)聲明泛型<E>,因此即使在泛型類中并未聲明泛型,編譯器也能夠正確識(shí)別泛型方法中識(shí)別的泛型。
showResult:在泛型類中聲明了一個(gè)泛型方法,使用泛型T,注意這個(gè)T是一種全新的類型,可以與泛型類中聲明的T不是同一種類型。
3.泛型方法與可變參數(shù)
定義printMsg方法,該方法接收可變參數(shù):
package com.victor.technology.generic;
public class GenericTest
{
public static void main(String[] args)
{
GenericTest gen = new GenericTest();
gen.printMsg("111", 222, "aaaa", "2323.4", 55.55);
}
public <T> void printMsg(T... args)
{
for (T t : args)
{
System.out.println(t);
}
}
}
4.靜態(tài)方法與泛型
1.靜態(tài)方法無法訪問類上定義的泛型,否則會(huì)報(bào)錯(cuò)

2.靜態(tài)方法跟普通方法一樣,需要使用泛型的話,必須將靜態(tài)方法也定義成泛型方法。
泛型接口
定義泛型接口:
package com.victor.technology.generic;
public interface Generic<T>
{
public T getValue();
}
使用例子:
public class GenericTest implements Generic<String>
{
//...
}
類型擦除
類型擦除的概念
類型擦除(Type Erasure): Java中的泛型基本上是在編譯期這個(gè)層次來實(shí)現(xiàn)的,在生成的Java字節(jié)碼中是不包含泛型中的類型信息。使用泛型的時(shí)候加上的類型參數(shù)會(huì)被編譯器在編譯的時(shí)候去掉,這個(gè)過程就被稱為類型擦除。
例如,List<String>和List<Integer>經(jīng)過擦除編譯后都是List.class
package com.victor.technology.generic;
import java.util.ArrayList;
import java.util.List;
public class GenericTest
{
public static void main(String[] args)
{
List<String> strList = new ArrayList<String>();
List<Integer> intList = new ArrayList<Integer>();
System.out.println(strList.getClass());
System.out.println(strList.getClass() == intList.getClass());
}
}
執(zhí)行結(jié)果如下:
class java.util.ArrayList
true
List<String>、List<T> 擦除后的類型為 List
List<String>[]、List<T>[] 擦除后的類型為 List[]
List<? extends E>、List<? super E> 擦除后的類型為 List<E>
List<T extends Serialzable & Cloneable> 擦除后類型為 List<Serializable>
類型擦除的原因
為什么使用類型擦除這種偽泛型?主要是為了兼容之前的版本,如果把在運(yùn)行期實(shí)現(xiàn)泛型,JVM需要大換血改動(dòng)量太大
類型擦除的影響
1.泛型不支持協(xié)變
數(shù)組是支持協(xié)變的,即Object[] arr = new Integer[5];語法是正確的,但是List<Object> list = new ArrayList<Integer>();會(huì)有編譯錯(cuò)誤。
這是因?yàn)槊總€(gè)數(shù)組在運(yùn)行時(shí)都知道它的確切類型,而泛型由于類型擦除機(jī)制不能明確具體的參數(shù)類型從而可能導(dǎo)致ClassCastException
假如List<Object> list = new ArrayList<Integer>();是正確的,list可以add整數(shù)和字符串,在調(diào)用get方法的時(shí)候就不知道它具體是哪個(gè)類型然后就要使用強(qiáng)轉(zhuǎn)換,如果將String強(qiáng)轉(zhuǎn)換為Integer就會(huì)拋異常。
2.不能實(shí)例化泛型
實(shí)例化類型參數(shù)的時(shí)候報(bào)錯(cuò),如下:

使用instanceof的時(shí)候也報(bào)錯(cuò):

3.對(duì)數(shù)組的限制
首先不能創(chuàng)建元素類型為類型參數(shù)的數(shù)組,其次不能創(chuàng)建特定類型的泛型引用數(shù)組。
即在泛型類中new T[]是會(huì)報(bào)錯(cuò)的,因?yàn)榫幾g器無法知道具體創(chuàng)建成什么類型的數(shù)組
其次List<Integer>[] list = new ArrayList<Integer>[]也是會(huì)報(bào)錯(cuò)的,因?yàn)長(zhǎng)ist<Integer>在JVM中等同于List<Object>擦除之后程序無法區(qū)分一個(gè)數(shù)組接收的是Integer類型還是String類型的元素。
4.泛型可以被繞過
因?yàn)榉盒驮诰幾g器會(huì)被擦除,所以可以在運(yùn)行期通過反射繞過泛型。
例如定義一個(gè)只接收String的列表List<String> list = new ArrayList<String>(),經(jīng)過編譯之后列表變?yōu)長(zhǎng)ist,然后通過反射就可以添加Integer類型的元素了