JAVA泛型

一文徹底搞懂Java泛型中的PECS原則(在坑里躺了多年終于爬出來了)

1.什么是泛型毁渗?

泛型,就我的理解就是類型參數(shù)化

一個栗子
      List arrayList = new ArrayList();
       arrayList.add("aaaa");
        arrayList.add(100);
        for(int i = 0; i< arrayList.size();i++){
            String item = (String)arrayList.get(i);
           System.out.println("泛型測試---item = " + item);      
    }

毫無疑問灸异,程序的運(yùn)行結(jié)果會以崩潰結(jié)束:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

ArrayList可以存放任意類型肺樟,例子中添加了一個String類型,添加了一個Integer類型疟暖,再使用時都以String的方式使用田柔,因此程序崩潰了。為了解決類似這樣的問題(在編譯階段就可以解決)欣舵,泛型應(yīng)運(yùn)而生摆屯。
我們將第一行聲明初始化list的代碼更改一下,編譯器會在編譯階段就能夠幫我們發(fā)現(xiàn)類似這樣的問題准验。

List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在編譯階段廷没,編譯器就會報(bào)錯

2.泛型特性

通過下面的例子可以證明颠黎,在編譯之后程序會采取去泛型化的措施滞项。也就是說Java中的泛型文判,只在編譯階段有效室梅。在編譯過程中,正確檢驗(yàn)泛型結(jié)果后赏殃,會將泛型的相關(guān)信息擦出仁热,并且在對象進(jìn)入和離開方法的邊界處添加類型檢查和類型轉(zhuǎn)換的方法勾哩。也就是說,泛型信息不會進(jìn)入到運(yùn)行時階段物蝙。

對此總結(jié)成一句話:泛型類型在邏輯上看以看成是多個不同的類型敢艰,實(shí)際上都是相同的基本類型钠导。

        List<String> stringList=new ArrayList<>();
        List<Integer> integerList=new ArrayList<>();
        if(stringList.getClass().equals(integerList.getClass())){
            //如果輸出亂碼,需要將文件設(shè)置成GB2312編碼格式
            System.out.println("類型相同--->證明了泛型只是在編譯階段有效,編譯成字節(jié)碼就
                 無效了");
        }
image.png

3.泛型的具體使用場景

3.1泛型類
/****
 *一個普通的java泛型類
 * 此處T可以隨便寫為任意標(biāo)識票堵,常見的如T悴势、E措伐、K、V等形式的參數(shù)常用于表示泛型
 在實(shí)例化泛型類時捧存,必須指定T的具體類型
 * @param <T>
 */

public   class Generics<T> {
    //key這個成員變量的類型為T,T的類型由外部指定
    private T key;

    public Generics(T key) {
        this.key = key;
    }

    public T getKey() {
        return key;
    }

}
        Generics<Integer> integerGenerics=new Generics<>(123456);
        Generics<String>  stringGenerics=new Generics<>("one_two_three_four_five_six");
        System.out.println("key is "+integerGenerics.getKey());
        System.out.println("key is "+stringGenerics.getKey());
注意:定義的泛型類昔穴,就一定要傳入泛型類型實(shí)參么?并不是這樣泳唠,在使用泛型的時候如果傳入泛型實(shí)參警检,則會根據(jù)傳入的泛型實(shí)參做相應(yīng)的限制害淤,此時泛型才會起到本應(yīng)起到的限制作用窥摄。如果不傳入泛型類型實(shí)參的話础淤,在泛型類中使用泛型的方法或成員變量定義的類型可以為任何的類型。
        Generics generics1=new Generics(111111);
        Generics generics2=new Generics("one_two_three_four_five_six");
        Generics generics3=new Generics(false);
        Generics generics4=new Generics(11.00f);
        System.out.println("key is "+generics1.getKey());
        System.out.println("key is "+generics2.getKey());
        System.out.println("key is "+generics3.getKey());
        System.out.println("key is "+generics4.getKey());
3.2泛型接口

泛型接口與泛型類的定義及使用基本相同币砂。泛型接口常被用在各種類的生產(chǎn)器(構(gòu)造方法)中决摧,可以看一個例子:

//定義一個泛型接口
public interface Generator<T> {
    public T next();
}

當(dāng)實(shí)現(xiàn)泛型接口的類凑兰,未傳入泛型實(shí)參時:

/**
 * 未傳入泛型實(shí)參時姑食,與泛型類的定義相同,在聲明類的時候则拷,需將泛型的聲明也一起加到類中
 * 即:class FruitGenerator<T> implements Generator<T>{
 * 如果不聲明泛型曹鸠,如:class FruitGenerator implements Generator<T>,編譯器會報(bào)錯:"Unknown class"
 */
class FruitGenerator<T> implements Generator<T>{
    @Override
    public T next() {
        return null;
    }
}

當(dāng)實(shí)現(xiàn)泛型接口的類宣旱,傳入泛型實(shí)參時:

/**
 * 傳入泛型實(shí)參時:
 * 定義一個生產(chǎn)器實(shí)現(xiàn)這個接口,雖然我們只創(chuàng)建了一個泛型接口Generator<T>
 * 但是我們可以為T傳入無數(shù)個實(shí)參浑吟,形成無數(shù)種類型的Generator接口笙纤。
 * 在實(shí)現(xiàn)類實(shí)現(xiàn)泛型接口時省容,如已將泛型類型傳入實(shí)參類型腥椒,則所有使用泛型的地方都要替換成傳入的實(shí)參類型
 * 即:Generator<T>候衍,public T next();中的的T都要替換成傳入的String類型。
 */
public class FruitGenerator implements Generator<String> {

    private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

    @Override
    public String next() {
        Random rand = new Random();
        return fruits[rand.nextInt(3)];
    }
}
3.3泛型方法

在java中,泛型類的定義非常簡單,但是泛型方法就比較復(fù)雜了像啼,泛型類邀杏,是在實(shí)例化類的時候指明泛型的具體類型响逢;

泛型方法棕孙,是在調(diào)用方法的時候指明泛型的具體類型 ,必須通過<T,K,E>的形式聲明。
常見的泛型方法:

//T可以是MapListBean類或者是MapListBean的子類
public <T extends MapListBean> void fetchData(T url) {

}

   public <T> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        //當(dāng)然這個例子舉的不太合適分歇,只是為了說明泛型方法的特性职抡。
        T test = container.getKey();
        return test;
    }

public <T,K> K showKeyName(Generic<T> container){
  
}

這些不是泛型方法:

//這不是一個泛型方法,沒有聲明泛型E
   public E setKey(E key){
             this.key = keu
   }
//這不是一個泛型方法误甚,沒有聲明泛型T
    public T getKey(){
            return key;
   }
 //這也不是一個泛型方法,這就是一個普通的方法擅威,只是使用了Generic<Number>這個泛型類做形參而已郊丛。
    public void showKeyValue1(Generic<Number> obj){
        Log.d("泛型測試","key value is " + obj.getKey());
    }



     /**
     * 這個方法是有問題的,編譯器會為我們提示錯誤信息:"UnKnown class 'E' "
     * 雖然我們聲明了<T>,也表明了這是一個可以處理泛型的類型的泛型方法导盅。
     * 但是只聲明了泛型類型T揍瑟,并未聲明泛型類型E,因此編譯器并不知道該如何處理E這個類型滤馍。**/
    public <T> T showKeyName(Generic<E> container){
       
    }

泛型類中的方法:

public class GenericFruit {
    class Fruit{
        @Override
        public String toString() {
            return "fruit";
        }
    }

    class Apple extends Fruit{
        @Override
        public String toString() {
            return "apple";
        }
    }

    class Person{
        @Override
        public String toString() {
            return "Person";
        }
    }

    class GenerateTest<T>{
        public void show_1(T t){
            System.out.println(t.toString());
        }

        //在泛型類中聲明了一個泛型方法巢株,使用泛型E熙涤,這種泛型E可以為任意類型。可以類型與T相同茸歧,也可以不同显沈。
        //由于泛型方法在聲明的時候會聲明泛型<E>,因此即使在泛型類中并未聲明泛型涤浇,編譯器也能夠正確識別泛型方法中識別的泛型魔慷。
        public <E> void show_3(E t){
            System.out.println(t.toString());
        }

        //在泛型類中聲明了一個泛型方法,使用泛型T蜻展,注意這個T是一種全新的類型邀摆,可以與泛型類中聲明的T不是同一種類型。
        public <T> void show_2(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args) {
        Apple apple = new Apple();
        Person person = new Person();

        GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
        //apple是Fruit的子類施逾,所以這里可以
        generateTest.show_1(apple);
        //編譯器會報(bào)錯汉额,因?yàn)榉盒皖愋蛯?shí)參指定的是Fruit,而傳入的實(shí)參類是Person
        //generateTest.show_1(person);

        //使用這兩個方法都可以成功
        generateTest.show_2(apple);
        generateTest.show_2(person);

        //使用這兩個方法也都可以成功
        generateTest.show_3(apple);
        generateTest.show_3(person);
    }
}
3.4 通配符
  • 通配符
public void showKeyValue1(Generic<Float> obj){
    Log.d("泛型測試","key value is " + obj.getKey());
}
  Generics<Integer> integerG=new Generics<>(345);
 Generics<Float>  floatG=new Generics<>(678f);

看上面的例子:

showKeyValue(gInteger );//此時必定報(bào)錯cannot be applied to Generic<java.lang.Float>

所有通配符在這個時候派上用場了整葡,我們可以:

public void showKeyValue1(Generic<?> obj){
    Log.d("泛型測試","key value is " + obj.getKey());
}

3.5.泛型中的PECS原則

3.5.1.PECS 是 Producer extends Consumer Super 的縮寫遭居, 生產(chǎn)者提供數(shù)據(jù)(get)旬渠,用extends ,消費(fèi)者使用數(shù)據(jù) (add)枪蘑,用super .
3.5.2.上邊界( ? extends T) 表示泛型的類型必須為T類型或者T的子類 【一般只用獲取元素】

為了更好的理解岳颇,我們先定義一些類

public class Fruit{}
public class Apple extends Fruit{}
public class Banana extends Fruit{}

例如:List<? extends Fruit>中 <? extends Fruit> 表示泛型的類型是Fruit類或者是Fruit的子類颅湘,我們給list賦值的時候,可以是new ArrayList<Fruit>(), new ArrayList<Apple>(),new ArrayList<Banana>().

  private static List<? extends Fruit>  getExtendsList(){
        List<? extends Fruit> list;
       list=new ArrayList<Apple>();
       list=new ArrayList<Banana>();
       list=new ArrayList<Fruit>();
      return list;
 }

List<? extends Fruit>允許我們將ArrayList<Fruit>(),ArrayList<Apple>(),ArrayList<Banana>()賦值給它瞻鹏。然而鹿寨,雖然我們可以從這個列表中獲取元素(因?yàn)槲覀冎浪鼈冎辽偈荈ruit類型),但是我們無法往列表中添加元素(除了null),因?yàn)榫幾g器不知道list的具體類型赫悄,可能是new ArrayList<Banana>().

public static void m1(List<? extends Fruit> list){
       Fruit fruit=list.get(0);//安全的涩蜘,因?yàn)槲覀冎懒斜碇械臄?shù)據(jù)類型至少是Fruit類型
   //  list.add(new Apple());//錯誤熏纯,編譯器不允許,list可能是new ArrayList<Banana>()
}
3.5.3.下邊界( ? super T) 表示泛型的類型必須為T類型或者T的父類 【一般只用于添加元素误窖,因?yàn)楂@取的元素類型只能是Object類型,意義不大】

例如:List<? super Fruit> 表示泛型的類型必須是Fruit類型或者是Fruit的父類霹俺,我們給list賦值的時候,可以是new ArrayList<Fruit>(),new ArrayList<Object>().

private static List<? super Fruit> getSuperList(){
      List<? super Fruit> list;
      list=new ArrayList<Fruit>();
      list=new ArrayList<Object>();
      return list;  
 }

List<? super Fruit> 允許我們將ArrayList<Fruit>(),ArrayList<Object>()賦值給它愈魏,但是沒法獲取特定類型的元素培漏,只能獲取Object類型的元素胡本。

 private static void m2(List<? super Fruit> list){ //list列表只能添加Fruit類型及其子類型
        list.add(new Apple()); //可以,因?yàn)锳pple是Fruit類型
        list.add(new Banana());//可以珊佣,因?yàn)锽anana是Fruit類型
       Object  item=list.get(0);//只能是Object,因?yàn)槲覀儾恢谰唧w的類型
}

4.深入理解泛型通配符

4.1.協(xié)變和逆變
  • <? extends T> 是協(xié)變的披粟,這意味著如果Apple是Fruit的子類,那么List<Apple> 也是 List<? extends Fruit> 的子類
  • <? super T> 是逆變的虫碉,這意味著如果Fruit是Apple的父類,那么List<Fruit>也是List<? super Apple>的父類须板。
4.2.泛型方法中的PECS (extends out super in )
   public static <T> void addToList(List<? super T> list,T item){
        list.add(item);
  }
  public static <T> void copyList(List<? extens T> source ,List<? super T>  destination){
        for(T item:source){
             destination.add(item);
        }
 }
Kotlin中的協(xié)變(out)和逆變(in)
  • 協(xié)變(out) 允許將T類型或者T類型的子類賦值給泛型
open class Animal {}
class Cat : Animal() {}
interface Container<out T>{
     fun getItem():T
}
fun main(){
    val catContainer:Container<Cat> =object:Container<Cat>{
          override fun getItem():Cat{
                 return Cat();
         }
    }
    val animalContainer:Container<Animal>=catContainer ;
    val animal:Animal =animalContainer.getItem();
    println(animal);
}
  • 逆變 (in) 允許將T類型或者T類型的父類賦值給泛型
interface Processor<in T> {
    fun process(item: T)
}
fun main() {
    val animalProcessor: Processor<Animal> = object : Processor<Animal> {
        override fun process(item: Animal) {
            println("Processing animal: $item")
        }
    }
    val catProcessor: Processor<Cat> = animalProcessor // 逆變
    catProcessor.process(Cat())
}
總結(jié)
  • List<? extends Fruit> list:表示泛型是Fruit或者Fruit的子類习瑰,一般用于獲取元素。
  • List<? super Fruit> list: 表示泛型是Fruit或者是Fruit的父類柠横,一般用于添加元素课兄。
  • List list :明確的泛型,可以獲取元素搬俊,也可以添加元素,是最常用的泛型唉擂。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玩祟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子藏鹊,更是在濱河造成了極大的恐慌勺卢,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宴抚,死亡現(xiàn)場離奇詭異菇曲,居然都是意外死亡抚吠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門喊式,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萧朝,“玉大人,你說我怎么就攤上這事献联『沃罚” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵原押,是天一觀的道長班眯。 經(jīng)常有香客問我,道長署隘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任违崇,我火速辦了婚禮羞延,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘伴箩。我一直安慰自己鄙漏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布巩步。 她就那樣靜靜地躺著椅野,像睡著了一般籍胯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘫怜,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音赠涮,去河邊找鬼。 笑死斜友,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鲜屏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惯殊,長吁一口氣:“原來是場噩夢啊……” “哼也殖!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起己儒,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤捆毫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后途样,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體省艳,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年赖晶,在試婚紗的時候發(fā)現(xiàn)自己被綠了遏插。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纠修。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖了牛,靈堂內(nèi)的尸體忽然破棺而出辰妙,到底是詐尸還是另有隱情,我是刑警寧澤密浑,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布尔破,位于F島的核電站浇衬,受9級特大地震影響餐济,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜梳星,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一滚朵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辕近,春花似錦、人聲如沸归粉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浅乔。三九已至,卻和暖如春靖苇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悼枢。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工馒索, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留名船,地道東北人绰上。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像渴邦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子谋梭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容