java中泛型的理解及使用

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文件,然后就可以獲取泛型類型了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 泛型(Generics)的型變是Java中比較難以理解和使用的部分,“神秘”的通配符,讓我看了幾遍《Java...
    珞澤珈群閱讀 8,130評(píng)論 12 51
  • 參數(shù)類型的好處 在 Java 引入泛型之前,泛型程序設(shè)計(jì)是用繼承實(shí)現(xiàn)的。ArrayList 類只維護(hù)一個(gè) Obje...
    杰哥長(zhǎng)得帥閱讀 956評(píng)論 0 3
  • ArrayList就是個(gè)泛型類,我們通過(guò)設(shè)定不同的類型,可以往集合里面存儲(chǔ)不同類型的數(shù)據(jù)類型(而且只能存儲(chǔ)設(shè)定的數(shù)...
    dinel閱讀 528評(píng)論 0 2
  • 1.泛型簡(jiǎn)介 問(wèn)題:在獲取用戶信息的API中,后臺(tái)給我們返回一個(gè)這樣形式的json字符串。{ "meta":...
    彼岸之城cyy閱讀 1,069評(píng)論 0 0
  • 簡(jiǎn)介 泛型的意思就是參數(shù)化類型,通過(guò)使用參數(shù)化類型創(chuàng)建的接口、類、方法,可以指定所操作的數(shù)據(jù)類型。比如:可以使用參...
    零度沸騰_yjz閱讀 3,409評(píng)論 1 15

友情鏈接更多精彩內(nèi)容