Android基礎(chǔ)-Java泛型

泛型的定義

泛型:參數(shù)化的類型晦闰。很簡單的一句話放祟,那么什么叫做“參數(shù)化的類型”呢?呻右。跪妥。。声滥。眉撵。

為什么需要泛型?

假設(shè)現(xiàn)在有這樣一個需求:把兩個整數(shù)進行相加并返回計算結(jié)果落塑。我們很輕松的就可以寫出如下代碼來完成此功能:

    public int addInt(int x, int y) {

        return x + y;
    }

后來有一天業(yè)務(wù)拓展纽疟,需要支持浮點數(shù)進行相加并返回計算結(jié)果。于是我們可以新增一個新的如下方法來完成新需求:

 public float addFloat(float x, float y) {

        return x + y;
    }

日子一天天的過去憾赁,又有了新的需求污朽,需要支持double類型的相加,那么我們依然可以依葫蘆畫瓢的再增加一個新的方法來完成double類型的數(shù)據(jù)相加龙考。誒蟆肆?等等矾睦。。颓芭。觀察上面的兩個方法除了參數(shù)的類型和返回值不同之外顷锰,其他的都相同(方法名是可以相同的,稱之為重載亡问,此處為了區(qū)分類型官紫,故寫作不同名稱)。于是為了解決這個問題州藕,java就引入了泛型機制束世,可以很好的解決重復(fù)代碼

我們平常用的最多的數(shù)據(jù)結(jié)構(gòu)恐怕就是List了床玻。例如:

        List list = new ArrayList();
        list.add("element0");
        list.add("element1");
        list.add(100);

這段代碼無論在編寫階段還是運行階段都是不會報錯的毁涉。

        for (int i = 0; i < list.size(); i++) {
            String value = (String) list.get(i);
            System.out.println("第" + (i + 1) + "個元素的值是: " + value);
        }

可是當(dāng)我們需要用上面的代碼遍歷輸出集合中元素的時候,卻會報出ClassCastException锈死。這時因為最后添加的一個元素是 int 類型贫堰,強轉(zhuǎn)為 String 類型肯定是會報錯的。于是這里就體現(xiàn)出了泛型的第二個好處:安全待牵。于是我們經(jīng)常如下去創(chuàng)建一個List:

        List<String> list = new ArrayList<>();
        list.add("element0");
        list.add("element1");
        list.add(100); // 編譯器會提示此行代碼報錯

并且加了泛型之后在取值的時候也不需要強制類型轉(zhuǎn)換了其屏。
綜上所述:1.適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼。2.在編譯期間就發(fā)現(xiàn)數(shù)據(jù)類型安全問題缨该。這也就是我們?yōu)槭裁匆褂梅盒偷脑颉?/p>

泛型的使用
  • 泛型類
public class GenericClass<T> {
    
    private T data;

    public GenericClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
  • 泛型接口
public interface GenericInterface<T> {

    T next();
}

關(guān)于泛型接口的實現(xiàn)有兩種:

public class GenericInterfaceImpl1 implements GenericInterface<String>{
    @Override
    public String next() {
        return null;
    }
}
public class GenericInterfaceImpl2<T> implements GenericInterface<T>{

    @Override
    public T next() {
        return null;
    }
}

這兩種方式的區(qū)別就是一個是在使用的時候才確定具體類型偎行,一個在聲明類的時候就確定了類型。

  • 泛型方法
    public <T> T genericMethod(T... t) {
        return t[t.length / 2];
    }

其中<T>是定義泛型方法所必須的贰拿,如果沒有的話蛤袒,即使這個方法帶有 T 妙真、或者 E 這種常見的泛型定義,那么它依然不是一個泛型方法。例如上面泛型類中的 getData() 或者 setData(T data),前面沒有<T>,它們依舊是普通的方法,只不過他們是定義在了泛型類中罷了溯壶。

類型變量的限定-用于方法上
    public static <T extends Comparable> T min(T a, T b) {
        if (a.compareTo(b) > 0) return a;else return b;
    }

extends 關(guān)鍵字從面相對象上嚴格來說是叫派生又跛。那么 T extends Comparable 就可以理解為派生自 Comparable 這個接口的子類端幼。故下面可以使用Comparable 接口中的 compareTo() 方法摹迷。假如尖括號中只有一個 T鲫寄,那么參數(shù)a是無法調(diào)用compareTo()方法的地来。這就是所謂的類型變量的限定
關(guān)于限定類型的使用有如下規(guī)則:

  • 可以有多個限定類型熙掺,中間用 & 符號隔開
  • 可以是類未斑,也可以是接口。如果是類的話需要放在第一個的位置
  • 有且只能有一個類币绩,接口則沒有限制蜡秽。因為java是單繼承府阀,多實現(xiàn)。
    下面是簡單示例:
    public static <T extends View & Comparable & Serializable> T min(T a, T b) {
        if (a.compareTo(b) > 0) return a;else return b;
    }

此時傳入的參數(shù)需要滿足是 View 的子類芽突,且同時實現(xiàn)了Comparable 和 Serializable 接口才能使用试浙。否則無法通過編譯。

類型變量的限定-用于類上
public class GenericClass<T extends Comparable> {

    private T data;

    public GenericClass(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public T min(T t) {
        if (this.data.compareTo(t) > 0) {
            return t;
        } else {
            return this.data;
        }
    }
}

這里是把上面泛型類進行了一個改造寞蚌,這時泛型的具體類型只能是 Comparable 的實現(xiàn)類田巴。

泛型的約束和局限性
  • 不能實例化類型變量
    還是以上面的泛型類為例。這時我們給它添加一個無參的構(gòu)造方法睬澡。
public class GenericClass<T> {

    private T data;

    public GenericClass() {

    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

假如我們想要在構(gòu)造方法中初始化 T 的實例是不允許的固额。編譯器會直接報錯:


  • 靜態(tài)域或靜態(tài)方法不能引用類型變量



    編譯器會直接報錯。這個問題的答案其實很簡單:泛型的具體類型是在創(chuàng)建出具體的泛型類的對象的時候才確定的煞聪,而靜態(tài)代碼的執(zhí)行是早于對象的創(chuàng)建的斗躏。那么虛擬機根本就不知道靜態(tài)的 T 是什么。但如果是靜態(tài)泛型方法則是可以的:

    public static <E> void print(E e) {
        System.out.println(e.toString());
    }
  • 基本數(shù)據(jù)類型不能作為泛型
        GenericClass<double> genericClass1 = new GenericClass<>(); // 報錯 基本類型不可以
        
        GenericClass<Double> genericClass2 = new GenericClass<>(); // 需要用其包裝類
  • 不能使用 instanceof 關(guān)鍵字
    通常我們需要判斷一個對象是不是某種類型的時候昔脯,都會用 instanceof 關(guān)鍵字來判斷啄糙。可是判斷某個對象是不是某個泛型類的類型時卻不可以云稚。例如:
        GenericClass<Float> genericFloat = new GenericClass<>();

        if (genericFloat instanceof GenericClass<Float>){ // 這一行編譯器會報錯

        }
  • 不能實例化泛型數(shù)組



    可以聲明泛型數(shù)組隧饼,但是卻不能實例化。這是 java 語法規(guī)定静陈。是不是很奇葩燕雁?

  • 泛型類不能繼承 Exception 或 Throwable



    非常簡單粗暴,編譯器直接提示泛型類不能派生自 Throwable鲸拥。

  • 不能捕獲泛型類對象



    但是下面這種寫法確實允許的:


通配符

假設(shè)現(xiàn)在有兩個如下類拐格,且存在繼承關(guān)系:

public class Animal {
    
}

public class Dog extends Animal{
        
}

Dog 是 Animal 的子類,那么問題來了:

        GenericClass<Animal> animalGeneric = new GenericClass<>();
        GenericClass<Dog> dogGeneric = new GenericClass<>();

請問GenericClass<Animal> 和 GenericClass<Dog> 之間存在繼承關(guān)系嗎刑赶?答案顯然是否定的捏浊。但是泛型類可以繼承或擴展其他泛型類,例如 List 和 ArrayList 之間的關(guān)系:

public class GenericClassChild<T> extends GenericClass<T>{
    
}

再假設(shè)現(xiàn)在有如下一個方法:

    public static <T> void set(GenericClass<Animal> genericClass) {

    }

那么下面兩種調(diào)用可以嗎撞叨?

        set(animalGeneric); // 1
        set(dogGeneric); // 2

很顯然金踪,第一種是肯定可以的。第二種就不可以了牵敷。于是為了解決這種問題就有了通配符的概念胡岔。

  • ? extends
    現(xiàn)在有如下4個類,且有如下繼承關(guān)系:



    假設(shè)現(xiàn)在有如下4個對象和對應(yīng)的打印方法:

        GenericClass<Fruit> fruit = new GenericClass<>();
        GenericClass<Apple> apple = new GenericClass<>();
        GenericClass<RedApple> redApple = new GenericClass<>();
        GenericClass<Orange> orange = new GenericClass<>();

        public static <T> void printExtends(GenericClass<? extends Apple> genericClass) {
      
        }

下面我們來看一下調(diào)用結(jié)果:


從上圖中我們可以得知 枷餐?extends 限定了傳入?yún)?shù)的上邊界姐军。那這樣使用有沒有什么限制呢?

可以看到當(dāng)我們嘗試著設(shè)置數(shù)據(jù)的時候是不允許的尖淘,當(dāng)我們要取數(shù)據(jù)的時候卻只可以用基類 Fruit 去接收奕锌。其實這也很好解釋。上面我們說了村生?extends 限定了上邊界惊暴,也就是說當(dāng)我們?nèi)?shù)據(jù)的時候,不管這個時候這個對象里有什么趁桃,但是一定是 Fruit 的子類辽话,根據(jù)多態(tài)的性質(zhì),可以用父類來接收子類卫病。至于設(shè)置數(shù)據(jù)的時候為什么不可以也就很好解釋了油啤。因為編譯器只知道傳入的是 Fruit 的子類,而至于傳入的具體是哪一個子類蟀苛,編譯器是不知道的益咬。至此我們可以總結(jié)一下:?extends 限定了傳入?yún)?shù)類型的上界帜平,用于安全的訪問數(shù)據(jù)

  • 幽告?super
        GenericClass<Fruit> fruit = new GenericClass<>();
        GenericClass<Apple> apple = new GenericClass<>();
        GenericClass<RedApple> redApple = new GenericClass<>();
        GenericClass<Orange> orange = new GenericClass<>();

        public static <T> void printSuper(GenericClass<? super Apple> genericClass) {

        }

下面我們來看一下調(diào)用結(jié)果:


可以看到?super正好是限定了傳入?yún)?shù)的下邊界裆甩。下面我們來看一下有什么限制:


不是說 super 是限定了下邊界嗎冗锁?怎么在設(shè)置數(shù)據(jù)的時候父類 Fruit 反而不行了呢?而子類 RedApple 卻可以呢嗤栓?這是因為所有類的父類都是Object冻河,而編譯器無法確定傳入的是什么類型,但是 Apple 和 Apple 的子類是可以安全的轉(zhuǎn)型為Apple的茉帅,可以滿足最下邊界叨叙,所以可以安全傳入。這也就是為什么最后獲取的時候直接得到的是 Object 担敌,而不是 Fruit 摔敛。至此我們可以總結(jié)一下:?super 限定了傳入?yún)?shù)類型的下界全封,用于安全的設(shè)置數(shù)據(jù)

虛擬機是如何實現(xiàn)泛型的马昙?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市刹悴,隨后出現(xiàn)的幾起案子行楞,更是在濱河造成了極大的恐慌,老刑警劉巖土匀,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件子房,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機证杭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門田度,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人解愤,你說我怎么就攤上這事镇饺。” “怎么了送讲?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵奸笤,是天一觀的道長。 經(jīng)常有香客問我哼鬓,道長监右,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任异希,我火速辦了婚禮健盒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宠互。我一直安慰自己味榛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布予跌。 她就那樣靜靜地躺著搏色,像睡著了一般。 火紅的嫁衣襯著肌膚如雪券册。 梳的紋絲不亂的頭發(fā)上频轿,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機與錄音烁焙,去河邊找鬼航邢。 笑死,一個胖子當(dāng)著我的面吹牛骄蝇,可吹牛的內(nèi)容都是我干的膳殷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼九火,長吁一口氣:“原來是場噩夢啊……” “哼赚窃!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起岔激,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤勒极,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后虑鼎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辱匿,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡键痛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了匾七。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片絮短。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖乐尊,靈堂內(nèi)的尸體忽然破棺而出戚丸,到底是詐尸還是另有隱情,我是刑警寧澤扔嵌,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站夺颤,受9級特大地震影響痢缎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜世澜,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一独旷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧寥裂,春花似錦嵌洼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诺舔,卻和暖如春鳖昌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背低飒。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工许昨, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人褥赊。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓糕档,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拌喉。 傳聞我的和親對象是個殘疾皇子速那,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

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