1. 泛型的一些概念
Java從1.5開(kāi)始加入了泛型俘陷,主要是解決類(lèi)型安全及擴(kuò)展問(wèn)題,它的本質(zhì)是參數(shù)化類(lèi)型的應(yīng)用观谦,也就是說(shuō)所操作的數(shù)據(jù)類(lèi)型被指定為一個(gè)參數(shù)拉盾。這種參數(shù)類(lèi)型可以用在類(lèi),接口和方法的創(chuàng)建中豁状,分別稱(chēng)為泛型類(lèi)捉偏,泛型接口和泛型方法。
泛型的一些特性:
泛型是類(lèi)型擦除的:Java的泛型只在編譯期有效泻红,它只在程序源碼中存在夭禽,在編譯后的字節(jié)碼文件中就已經(jīng)替換為原來(lái)的原生類(lèi)型。并且在對(duì)象的地方插入了強(qiáng)制轉(zhuǎn)型代碼谊路。因此對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō)List<String>和List<Int>就是同一個(gè)類(lèi)讹躯。
泛型識(shí)別:為了解決泛型的識(shí)別問(wèn)題,Java引入了Signature,LocalVaribaleTypeTable等新的屬性用于解決參數(shù)類(lèi)型的識(shí)別問(wèn)題。Signature的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名潮梯,這個(gè)屬性中保存的參數(shù)類(lèi)型并不是原生類(lèi)型骗灶,而是包括了參數(shù)化類(lèi)型的信息。從Signature屬性可以得知秉馏,所謂的擦除僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦鎖耙旦,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,所以還是通過(guò)class對(duì)象反射得到泛型信息萝究。
泛型不是協(xié)變的:如String是Object的子類(lèi)免都,但是List<String>并不是List<Object>的子類(lèi)型,它們是2個(gè)單獨(dú)的類(lèi)型帆竹。
2. 泛型的使用
泛型類(lèi)及泛型接口
public class Shop<T> {
//商品數(shù)量
int count;
//抽象商品
T goods;
public T get(){
return t;
}
}
假如有一個(gè)Shop類(lèi)琴昆,現(xiàn)在并不明確是什么類(lèi)型的店,里面定義了賣(mài)物品的數(shù)量int類(lèi)型的count,以及不確定什么類(lèi)型的商品goods馆揉。
這里有個(gè)問(wèn)題业舍,不確定類(lèi)型,但是必須有類(lèi)型升酣。否則編譯器就會(huì)報(bào)錯(cuò)舷暮。所以要給goods一個(gè)類(lèi)型,因?yàn)椴淮_定噩茄,所以可以先用一個(gè)代號(hào)代替下面,等實(shí)際類(lèi)型確定時(shí)再替換掉,這里使用T绩聘。但是這個(gè)T只是一個(gè)代號(hào)沥割,編譯器不可能認(rèn)識(shí),所以要在類(lèi)名的右邊用<>包裹T凿菩,告訴編譯器T是一個(gè)類(lèi)型的代號(hào)机杜,然后就可以在類(lèi)中使用T代表某種類(lèi)型了。
這里的T不具備特殊意義衅谷,只是到時(shí)會(huì)被替換的實(shí)際類(lèi)型的代號(hào)椒拗,所以它可以隨便寫(xiě)成A,B获黔,C蚀苛,D等等其他任何形式。
再繼續(xù)看玷氏,泛型T可以代表任何類(lèi)型堵未,這個(gè)范圍太廣,如果要限定是某一種類(lèi)型盏触,或符合某些特性呢渗蟹?侦厚,如是一家書(shū)店,所以如果要限定某種類(lèi)型的話就要對(duì)T進(jìn)行限制:
public class Shop<T extends BookShop> {
//商品數(shù)量
int count;
//抽象商品
T goods;
public T get(){
return goods;
}
}
使用extends關(guān)鍵字拙徽,限定了T只能是BookShop類(lèi)型刨沦,那么如果要求即是書(shū)店,又是咖啡店呢:
//如果有2種特性膘怕,直接用&符號(hào)連接,BookShop為類(lèi)想诅,CoffeShop為接口
public class Shop<T extends BookShop & CoffeShop> {
//商品數(shù)量
int count;
//抽象商品
T goods;
public T get(){
return goods;
}
}
直接使用&符號(hào)連接即可,這里的extends遵循Java的單繼承原則岛心,BookShop和CoffeShop只能一個(gè)是類(lèi)来破,另外個(gè)是接口,當(dāng)然接口是可以多實(shí)現(xiàn)的忘古,繼續(xù)用&連接即可徘禁,如添加Rest屬性:
//如果有2種特性,直接用&符號(hào)連接,BookShop為類(lèi)髓堪,CoffeShop送朱,Rest為接口,遵循Java的單繼承干旁,多實(shí)現(xiàn)原則
public class Shop<T extends BookShop & CoffeShop & Rest> {
//商品數(shù)量
int count;
//抽象商品
T goods;
public T get(){
return goods;
}
}
再來(lái)看驶沼,假如要開(kāi)一個(gè)分店,BranchShop:
public class BranchShop<B> extends Shop{
int count ;
B goods;
@Override
public BookShop get() {
return super.get();
}
}
可以看到BranchShop聲明了自己的泛型B(寫(xiě)在類(lèi)右邊争群,用<>包裹)但是重寫(xiě)的get()返回值類(lèi)型變成了BookShop回怜,如果BranchShop需要get()的返回值類(lèi)型是泛型類(lèi)型呢?:
public class BranchShop<B> extends Shop{
int count ;
B goods;
//這里不會(huì)報(bào)錯(cuò)
public B getGoods(){
return goods;
}
@Override
//這里會(huì)報(bào)錯(cuò)
public B get() {
//這里也會(huì)報(bào)錯(cuò)
return super.get();
}
}
getGoods()不會(huì)報(bào)錯(cuò)换薄,但是get()會(huì)報(bào)錯(cuò)玉雾,因?yàn)間et是父Shop的方法,而Shop的泛型T是這樣的:
T extends BookShop & CoffeShop & Rest
它限制了范圍轻要,所以如果要重寫(xiě)父類(lèi)的方法复旬,也要和父類(lèi)的泛型保持一樣的限制(也可以不重寫(xiě),不限制伦腐,如getGoods()):
public class BranchShop<B extends BookShop & CoffeShop & Rest> extends Shop{
int count ;
B goods;
public B getGoods(){
return goods;
}
@Override
//這里不報(bào)錯(cuò)了
public B get() {
//這里還是報(bào)錯(cuò)
return super.get();
}
}
可以看到當(dāng)B和父類(lèi)的T同步了限制之后赢底,返回值類(lèi)型不報(bào)錯(cuò)了失都,但是super.get()還是報(bào)錯(cuò)柏蘑,這是因?yàn)橹罢f(shuō)過(guò),要使用泛型必須在類(lèi)的右邊聲明粹庞,BranchShop在自己右邊聲明了<B extends BookShop & CoffeShop & Rest>咳焚,但是在BranchShop中,父類(lèi)Shop并沒(méi)有聲明庞溜,沒(méi)有聲明就沒(méi)法使用革半,這里子類(lèi)把父類(lèi)的泛型類(lèi)型丟失了碑定,所以要給它加上聲明:
public class BranchShop<B extends BookShop & CoffeShop & Rest> extends Shop<B>{
int count ;
B goods;
public B getGoods(){
return goods;
}
@Override
public B get() {
return super.get();
}
}
直接在Shop后聲明<B>就OK,這里的泛型就不能隨便寫(xiě)又官,必須和BranchShop的聲明保持一致延刘,也就是必須寫(xiě)成B。
A:public class BranchShop<B extends BookShop & CoffeShop & Rest> extends Shop<B>
B:public class BranchShop<B> extends Shop<B extends BookShop & CoffeShop & Rest>
至于Shop<>,里只能和BranchShop保持一致六敬,以及為什么不寫(xiě)成B碘赖,而寫(xiě)成A這樣,可以簡(jiǎn)單理解為外构,泛型必須聲明在類(lèi)的右邊且必須聲明才能使用普泡。所以Shop<>它是使用泛型,而不是聲明审编,所以不能隨便寫(xiě)成一個(gè)沒(méi)有聲明的類(lèi)型撼班,所以只能是B。所以限制條件也必須加在BranchShop后垒酬,也就是A寫(xiě)法砰嘁。
2. 泛型方法
如果類(lèi)沒(méi)有聲明泛型類(lèi)型,那么如何在方法里使用泛型勘究?般码,看一下一個(gè)常用的方法findViewById():
public <T extends View> T findViewById(@IdRes int id) {
return this.getDelegate().findViewById(id);
}
可以看到,是在返回值前面聲明了T乱顾,并且限制了T extends View,然后返回值就可以使用T了板祝。
3. 通配符
- 上界通配符
public class FruitShop {
//Apple集合,Apple繼承自Fruit
List<Apple> apples;
//Orange集合走净,Orange繼承自Fruit
List<Orange> oranges;
//這樣寫(xiě)沒(méi)法遍歷Apple和Orange集合
public void getFruit(List<Fruit> fruits){
}
}
假如有一個(gè)水果店券时,需要一個(gè)通用的方法遍歷每一種水果,但是由于泛型不是協(xié)變的伏伯,所以上面的getFruit()是沒(méi)法遍歷Apple和Orange集合的橘洞,這里要解決這個(gè)問(wèn)題,要用到? extends關(guān)鍵字:
public class FruitShop {
List<Apple> apples;
List<Orange> oranges;
public void getFruit(List<? extends Fruit> fruits){
for (int i = 0; i < fruits.size(); i++) {
//得到每一個(gè)fruit
Fruit fruit = fruits.get(i);
}
}
}
使用? extends Fruit代表了Fruit某個(gè)字類(lèi)型说搅,所以只要是Fruit子類(lèi)型都可以(每個(gè)類(lèi)型都是自身的子類(lèi)型)進(jìn)行遍歷炸枣。
但是上界通配符有一個(gè)問(wèn)題:
public class FruitShop<E> {
List<Apple> apples;
List<Orange> oranges;
public void getFruit(List<? extends Fruit> fruits){
//不報(bào)錯(cuò)
fruits.get(0);
//這里報(bào)錯(cuò)
fruits.add(new Orange());
}
}
在getFruit()接收了一個(gè)Fruit一個(gè)子類(lèi)型的List,但是只知道是Fruit的子類(lèi)型弄唧,具體是哪種子類(lèi)型是不知道的适肠,如上圖,很可能傳入了一個(gè)Apple的的List候引,但是有可能添加進(jìn)一個(gè)Orange侯养,所以Java不允許這樣操作。
這就是上界通配符澄干?extends T,限制了逛揩?只能是T或者T的子類(lèi)柠傍,并且只能取內(nèi)容,不能存內(nèi)容
- 下界通配符
public void setFruit(List<? super Fruit> fruits){
fruits.add(new Apple());
Object object = fruits.get(0);
}
和上界通配符剛好相反辩稽,下屆通配符代表可以接受Fruit及它的所有超類(lèi)型惧笛。
它的特性是可以存東西,但是不能取逞泄,或者只能用Object接收徐紧,但是類(lèi)型信息會(huì)全部丟失。因?yàn)榇_定取的到底是什么類(lèi)型炭懊。
- 無(wú)界通配符
//無(wú)界通配符
public void setFruit(List<?> fruits){
//這行報(bào)錯(cuò)
fruits.add(new Apple());
//同樣只能用Object接收
Object o = fruits.get(0);
}
并级?代表不進(jìn)行任何限制,可以是任何類(lèi)型侮腹。所以它既不能存嘲碧,也不能取(或者取出的值元素只能用Object接收)父阻。
但是可以通過(guò)寫(xiě)一個(gè)幫助類(lèi)愈涩,來(lái)達(dá)到讓?zhuān)磕苋〉墓δ埽?/p>
public void setFruit(List<?> fruits){
//這行報(bào)錯(cuò)
fruits.add(fruits.get(0));
//只能獲取到Object類(lèi)型
Object o = fruits.get(0);
setFruitHelper(fruits);
}
public <E> void setFruitHelper(List<E> e){
//這行不報(bào)錯(cuò)
e.add(e.get(0));
//可以獲取到T類(lèi)型
T t = e.get(0);
}
setFruitHelper()知道e列表中取出的任何值均為E類(lèi)型,并且知道E類(lèi)型的任何值放進(jìn)列表都是安全的加矛。
無(wú)界通配符也有要注意的問(wèn)題:
public void test(){
//構(gòu)造一個(gè)下界通配符集合
List<? super Fruit> f =new ArrayList<>();
//添加Apple
f.add(new Apple());
//添加Orange
f.add(new Orange());
//把下屆通配符集合傳入無(wú)界通配符集合里
setFruit(f);
}
public void setFruit(List<?> fruits){
//調(diào)用Helper方法
setFruitHelper(fruits);
}
public <T> void setFruitHelper(List<T> e){
//在這里執(zhí)行g(shù)et()以及add()
//取出的是Apple
T t = e.get(0);
//添加的是Apple
e.add(e.get(0));
//取出的是Orange
T t = e.get(1);
//添加的是Orange
e.add(e.get(1));
}
可以看到通過(guò)setFruitHelper()履婉,規(guī)避了下界通配符不能取的問(wèn)題。使用的時(shí)候需要注意斟览。
3. 獲取泛型類(lèi)型
前面說(shuō)過(guò)毁腿,泛型在運(yùn)行期間是擦除的,但是會(huì)保存在class文件里苛茂,所以可以從class里獲取
1. 獲取類(lèi)及接口泛型已烤,Java提供了2個(gè)關(guān)于泛型的方法:
- getGenericSuperclass:返回此類(lèi)所表示的實(shí)體的直接超類(lèi)的類(lèi)型,看一下代碼
//part1:AppleShop自身的泛型T妓羊,明確了父類(lèi)FruitShop的泛型Apple
public class AppleShop<T> extends FruitShop<Apple> {
}
//part 2
//獲取AppleShop的class
Class<AppleShop> appleShopClass = AppleShop.class;
//獲取type
Type genericSuperclass = appleShopClass.getGenericSuperclass();
//獲取具體type的數(shù)組結(jié)合
Type[] actualTypeArguments = ((ParameterizedType)genericSuperclass).getActualTypeArguments();
可以看到在part1中胯究,AppleShop類(lèi)聲明了自身的泛型T,明確了父類(lèi)的泛型類(lèi)型為Apple
然后在part2中躁绸,先獲取了AppleShop的class裕循,class通過(guò)調(diào)用getGenericSuperclass(),返回了Type净刮。
然后通過(guò)type再獲取具體的泛型數(shù)組剥哑,由于這里只有一個(gè)泛型,所以取第一個(gè)actualTypeArguments[0],分別看一下它們的結(jié)果:
type:xxx.xxx.xxx.FruitShop<xxx.xxx.xxx.Apple>
actualTypeArguments[0]:xxx.xxx.xxx.Apple
可以看到type包含了父類(lèi)FruitShop信息庭瑰,actualTypeArguments[0]返回了具體泛型類(lèi)型星持。
這里要注意一點(diǎn),這個(gè)方法獲取的是明確的父類(lèi)泛型弹灭,不是自身聲明的泛型類(lèi)型榕暇。因?yàn)樽陨矸盒皖?lèi)型這時(shí)還并沒(méi)有確定饱须。
- getGenericInterfaces:返回此類(lèi)直接實(shí)現(xiàn)的所有接口類(lèi)型,看一下代碼:
//part1:明確了父類(lèi)FruitShop的泛型Apple
public class AppleShop implements Rest<Apple> {
}
//part2:
//獲取AppleShop的class
Class<AppleShop> appleShopClass = AppleShop.class;
//獲取type數(shù)組(因?yàn)榭赡軐?shí)現(xiàn)多個(gè)接口,所以是數(shù)組)
Type[] genericInterfaces = appleShopClass.getGenericInterfaces();
//取第一個(gè)的type
ParameterizedType genericInterface = (ParameterizedType) genericInterfaces[0];
//獲取具體type的數(shù)組結(jié)合
Type[] actualTypeArguments = genericInterface.getActualTypeArguments();
看一下獲取結(jié)果:
type:xxx.xxx.xxx.FruitShop<xxx.xxx.xxx.Apple>
actualTypeArguments[0]:xxx.xxx.xxx.Apple
和getGenericSuperclass的過(guò)程和結(jié)果都是一樣的奈惑。
2. 獲取方法和成員變量的泛型
方法及成員變量的泛型,可以通過(guò)反射獲取拐叉。
public class AppleShop<T> extends FruitShop<Apple> {
//成員變量泛型
List<Apple> apples = new ArrayList<>();
//方法泛型毫深,包括返回值泛型以及參數(shù)泛型
public List<Apple> getApples(List<Apple> count){
return apples;
}
}
看一下如何獲取的代碼:
//獲取class
Class<AppleShop> appleShopClass = AppleShop.class;
try {
//獲取apples成員變量,這里只是測(cè)試驾诈,使用了獲取單個(gè)成員變量的方法
Field apples = appleShopClass.getDeclaredField("apples");
//獲取成員變量的type
Type genericType = apples.getGenericType();
//獲取成員變量泛型的具體type
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
}catch (Exception e){
}
try{
//獲取getApples(),這里只是測(cè)試缠诅,使用了獲取單個(gè)方法的方法
Method getApples = appleShopClass.getMethod("getApples", new Class[]{List.class});
//獲取方法參數(shù)的Type集合
Type[] genericParameterTypes = getApples.getGenericParameterTypes();
//取第一個(gè)參數(shù)的具體Type
ParameterizedType genericParameterType = (ParameterizedType) genericParameterTypes[0];
//取第一個(gè)參數(shù)的具體類(lèi)型集合
Type[] actualTypeArguments1 = genericParameterType.getActualTypeArguments();
//獲取getApples()的返回值Type
Type genericReturnType = getApples.getGenericReturnType();
//獲取返回值具體類(lèi)型的Type集合
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
}catch (Exception e){
}
以上就是獲取成員變量以及方法返回值,方法參數(shù)泛型類(lèi)型的方式乍迄。
3. 運(yùn)行時(shí)代碼的泛型
前面的都是通過(guò)class獲取的泛型類(lèi)型管引,但是有些代碼是運(yùn)行時(shí)才確定的,如下:
public List<Apple> getApples(List<Apple> count){
//這里運(yùn)行時(shí)才會(huì)確定
List <Orange> oranges=new ArrayList<>();
return apples;
}
運(yùn)行時(shí)才能確定的闯两,通過(guò)之前的方法就沒(méi)法獲取褥伴。
還是前面說(shuō)過(guò),class會(huì)保留泛型信息,那么這里通過(guò)建立匿名內(nèi)部類(lèi)的方式漾狼,然后就會(huì)產(chǎn)生class文件重慢,從而讓泛型類(lèi)型保存起來(lái),如下:
public List<Apple> getApples(List<Apple> count){
//通過(guò)在后面加一個(gè){}的方式建立一個(gè)匿名內(nèi)部類(lèi)
List <Apple> applesList=new ArrayList<Apple>(){};
//獲取type,和之前一樣
Type genericSuperclass = applesList.getClass().getGenericSuperclass();
return apples;
}
通過(guò)建立一個(gè)匿名內(nèi)部類(lèi)的方式逊躁,產(chǎn)生class文件似踱,然后就可以獲取泛型類(lèi)型了。