1. 泛型的一些概念
Java從1.5開(kāi)始加入了泛型,主要是解決類型安全及擴(kuò)展問(wèn)題,它的本質(zhì)是參數(shù)化類型的應(yīng)用,也就是說(shuō)所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)。這種參數(shù)類型可以用在類,接口和方法的創(chuàng)建中,分別稱為泛型類,泛型接口和泛型方法。
泛型的一些特性:
泛型是類型擦除的:Java的泛型只在編譯期有效,它只在程序源碼中存在,在編譯后的字節(jié)碼文件中就已經(jīng)替換為原來(lái)的原生類型。并且在對(duì)象的地方插入了強(qiáng)制轉(zhuǎn)型代碼。因此對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō)List<String>和List<Int>就是同一個(gè)類。
泛型識(shí)別:為了解決泛型的識(shí)別問(wèn)題,Java引入了Signature,LocalVaribaleTypeTable等新的屬性用于解決參數(shù)類型的識(shí)別問(wèn)題。Signature的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名,這個(gè)屬性中保存的參數(shù)類型并不是原生類型,而是包括了參數(shù)化類型的信息。從Signature屬性可以得知,所謂的擦除僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦鎖,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,所以還是通過(guò)class對(duì)象反射得到泛型信息。
泛型不是協(xié)變的:如String是Object的子類,但是List<String>并不是List<Object>的子類型,它們是2個(gè)單獨(dú)的類型。
2. 泛型的使用
泛型類及泛型接口
public class Shop<T> {
//商品數(shù)量
int count;
//抽象商品
T goods;
public T get(){
return t;
}
}
假如有一個(gè)Shop類,現(xiàn)在并不明確是什么類型的店,里面定義了賣物品的數(shù)量int類型的count,以及不確定什么類型的商品goods。
這里有個(gè)問(wèn)題,不確定類型,但是必須有類型。否則編譯器就會(huì)報(bào)錯(cuò)。所以要給goods一個(gè)類型,因?yàn)椴淮_定,所以可以先用一個(gè)代號(hào)代替,等實(shí)際類型確定時(shí)再替換掉,這里使用T。但是這個(gè)T只是一個(gè)代號(hào),編譯器不可能認(rèn)識(shí),所以要在類名的右邊用<>包裹T,告訴編譯器T是一個(gè)類型的代號(hào),然后就可以在類中使用T代表某種類型了。
這里的T不具備特殊意義,只是到時(shí)會(huì)被替換的實(shí)際類型的代號(hào),所以它可以隨便寫(xiě)成A,B,C,D等等其他任何形式。
再繼續(xù)看,泛型T可以代表任何類型,這個(gè)范圍太廣,如果要限定是某一種類型,或符合某些特性呢?,如是一家書(shū)店,所以如果要限定某種類型的話就要對(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類型,那么如果要求即是書(shū)店,又是咖啡店呢:
//如果有2種特性,直接用&符號(hào)連接,BookShop為類,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è)是類,另外個(gè)是接口,當(dāng)然接口是可以多實(shí)現(xiàn)的,繼續(xù)用&連接即可,如添加Rest屬性:
//如果有2種特性,直接用&符號(hào)連接,BookShop為類,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ě)在類右邊,用<>包裹)但是重寫(xiě)的get()返回值類型變成了BookShop,如果BranchShop需要get()的返回值類型是泛型類型呢?:
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ě)父類的方法,也要和父類的泛型保持一樣的限制(也可以不重寫(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和父類的T同步了限制之后,返回值類型不報(bào)錯(cuò)了,但是super.get()還是報(bào)錯(cuò),這是因?yàn)橹罢f(shuō)過(guò),要使用泛型必須在類的右邊聲明,BranchShop在自己右邊聲明了<B extends BookShop & CoffeShop & Rest>,但是在BranchShop中,父類Shop并沒(méi)有聲明,沒(méi)有聲明就沒(mé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)單理解為,泛型必須聲明在類的右邊且必須聲明才能使用。所以Shop<>它是使用泛型,而不是聲明,所以不能隨便寫(xiě)成一個(gè)沒(méi)有聲明的類型,所以只能是B。所以限制條件也必須加在BranchShop后,也就是A寫(xiě)法。
2. 泛型方法
如果類沒(mé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è)字類型,所以只要是Fruit子類型都可以(每個(gè)類型都是自身的子類型)進(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è)子類型的List,但是只知道是Fruit的子類型,具體是哪種子類型是不知道的,如上圖,很可能傳入了一個(gè)Apple的的List,但是有可能添加進(jìn)一個(gè)Orange,所以Java不允許這樣操作。
這就是上界通配符?extends T,限制了?只能是T或者T的子類,并且只能取內(nèi)容,不能存內(nèi)容
- 下界通配符
public void setFruit(List<? super Fruit> fruits){
fruits.add(new Apple());
Object object = fruits.get(0);
}
和上界通配符剛好相反,下屆通配符代表可以接受Fruit及它的所有超類型。
它的特性是可以存東西,但是不能取,或者只能用Object接收,但是類型信息會(huì)全部丟失。因?yàn)榇_定取的到底是什么類型。
- 無(wú)界通配符
//無(wú)界通配符
public void setFruit(List<?> fruits){
//這行報(bào)錯(cuò)
fruits.add(new Apple());
//同樣只能用Object接收
Object o = fruits.get(0);
}
?代表不進(jìn)行任何限制,可以是任何類型。所以它既不能存,也不能?。ɑ蛘呷〕龅闹翟刂荒苡肙bject接收)。
但是可以通過(guò)寫(xiě)一個(gè)幫助類,來(lái)達(dá)到讓?能取的功能:
public void setFruit(List<?> fruits){
//這行報(bào)錯(cuò)
fruits.add(fruits.get(0));
//只能獲取到Object類型
Object o = fruits.get(0);
setFruitHelper(fruits);
}
public <E> void setFruitHelper(List<E> e){
//這行不報(bào)錯(cuò)
e.add(e.get(0));
//可以獲取到T類型
T t = e.get(0);
}
setFruitHelper()知道e列表中取出的任何值均為E類型,并且知道E類型的任何值放進(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. 獲取泛型類型
前面說(shuō)過(guò),泛型在運(yùn)行期間是擦除的,但是會(huì)保存在class文件里,所以可以從class里獲取
1. 獲取類及接口泛型,Java提供了2個(gè)關(guān)于泛型的方法:
- getGenericSuperclass:返回此類所表示的實(shí)體的直接超類的類型,看一下代碼
//part1:AppleShop自身的泛型T,明確了父類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類聲明了自身的泛型T,明確了父類的泛型類型為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包含了父類FruitShop信息,actualTypeArguments[0]返回了具體泛型類型。
這里要注意一點(diǎn),這個(gè)方法獲取的是明確的父類泛型,不是自身聲明的泛型類型。因?yàn)樽陨矸盒皖愋瓦@時(shí)還并沒(méi)有確定。
- getGenericInterfaces:返回此類直接實(shí)現(xiàn)的所有接口類型,看一下代碼:
//part1:明確了父類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ù)的具體類型集合
Type[] actualTypeArguments1 = genericParameterType.getActualTypeArguments();
//獲取getApples()的返回值Type
Type genericReturnType = getApples.getGenericReturnType();
//獲取返回值具體類型的Type集合
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
}catch (Exception e){
}
以上就是獲取成員變量以及方法返回值,方法參數(shù)泛型類型的方式。
3. 運(yùn)行時(shí)代碼的泛型
前面的都是通過(guò)class獲取的泛型類型,但是有些代碼是運(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)部類的方式,然后就會(huì)產(chǎn)生class文件,從而讓泛型類型保存起來(lái),如下:
public List<Apple> getApples(List<Apple> count){
//通過(guò)在后面加一個(gè){}的方式建立一個(gè)匿名內(nèi)部類
List <Apple> applesList=new ArrayList<Apple>(){};
//獲取type,和之前一樣
Type genericSuperclass = applesList.getClass().getGenericSuperclass();
return apples;
}
通過(guò)建立一個(gè)匿名內(nèi)部類的方式,產(chǎn)生class文件,然后就可以獲取泛型類型了。