Java泛型莺匠,你都了解了嗎金吗?

大綱
一、為什么需要泛型趣竣?泛型的優(yōu)點(diǎn)
二摇庙、泛型定義
三、限定"類型變量"
四遥缕、泛型中的約束和局限性
五卫袒、泛型類型的繼承規(guī)則和通配符類型
六、虛擬機(jī)是如何實(shí)現(xiàn)泛型的单匣?-類型擦除
七夕凝、類型擦除與多態(tài)的沖突和解決方法

一、 為什么需要泛型户秤?泛型的優(yōu)點(diǎn)

  1. 適用于多種類型執(zhí)行相同的代碼

  2. 比如int相加 float相加 可以抽取出一個泛型方法

public static  <T> T add(T x, T y)  {
}
  1. 比如SharedPreference set get操作 對于不同類型 可以抽取一個公用的方法
public static <T> T getPrefValue(@NonNull SharedPreferences pref, @NonNull String key, @NonNull T t) {
    Objects.requireNonNull(pref);
    Objects.requireNonNull(key);
    Objects.requireNonNull(t);
    if (t instanceof String) {
        String str = pref.getString(key, (String) t);
        t = (T) str;
    } else if (t instanceof Integer) {
        Integer in = pref.getInt(key, (Integer) t);
        t = (T) in;
    } else if (t instanceof Long) {
        Long lon = pref.getLong(key, (Long) t);
        t = (T) lon;
    } else if (t instanceof Float) {
        Float fl = pref.getFloat(key, (Float) t);
        t = (T) fl;
    } else if (t instanceof Boolean) {
        Boolean bl = pref.getBoolean(key, (Boolean) t);
        t = (T) bl;
    } else if (t instanceof Set) {
        t = (T) pref.getStringSet(key, (Set<String>) t);
    } else {
        throw new IllegalArgumentException("getPrefValue fail ! Value Type not supported");
    }
    return t;
}
  1. 指定限制的類型码秉,插入錯誤的數(shù)據(jù)類型,能夠在編譯期間就發(fā)現(xiàn)錯誤虎忌。不至于在運(yùn)行時才發(fā)現(xiàn)異常泡徙。
List list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add(800);
for (int i = 0; i < list.size(); i++) {
    System.out.println((String)list.get(i));
}
運(yùn)行時拋出異常:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

二橱鹏、泛型定義

  1. 泛型類和泛型接口

    在類名或接口后加一個<T>

public class NormalGeneric<T> {

    private T mData;
}
public interface IGeneric<T> {

    T getData();
}
//也可聲明多個泛型
public class MultiGeneric<K, V> {

    private K mKey;

    private V mValue;

    public void setKeyValue(K key, V value) {
        mKey = key;
        mValue = value;
    }
}

  1. 泛型方法
  • 在返回值前用尖括號聲明泛型 同樣可定義多個
public <T> T getZ(T t) {
    //泛型方法
    return null;
}
publid <K,V> void setKeyValue(K key,V value){
    //todo
}

注意泛型方法

public T get(T t){}//注意 這不是一個泛型方法 這里的T 沒有在方法中聲明膜蠢,是屬于類聲明的泛型堪藐。

public <T> T get(){}//這才是一個泛型方法

  1. 泛型類以及其內(nèi)部泛型方法同時聲明泛型T,但泛型方法的T可以是個全新的類型,可與泛型類中的聲明T不是一個類型挑围。
public class GenericType<T> {

    private T mData;
    //備注  T可以是全新的類型礁竞,與類聲明的T不沖突
    public <T> void test(T t) {
    }
}

三、限定"類型變量"

對傳入的方法做限定杉辙。(看圖示理解)

image

支持多個限定模捂,T 可以繼承類也可以實(shí)現(xiàn)接口,但只能有一個類蜘矢,并要放在最前面狂男,后面的接口用&分割

public <K, V extends ArrayList & Comparable & Iterable> void set(K k, V value) {
}
ArrayList是類,Comparable和Iterable是接口

ArrayList是類品腹,Comparable和Iterable是接口

四岖食、泛型中的約束和局限性

編譯器強(qiáng)制規(guī)定:

  1. 不能實(shí)例化類型變量 new T() 不可以
  2. 靜態(tài)域或者方法里不能引用類型變量

privete state T getInstance();//不可以

為什么?泛型的類型在對象創(chuàng)建時舞吭,才知道具體的類型泡垃。而static在類加載就被執(zhí)行了 在構(gòu)造方法之前,所以這時還不知道具體類型羡鸥,但如果靜態(tài)方法本身就是一個泛型方法就可以蔑穴。

public static <T> T getInstance(){

}//可行

  1. 泛型不能用instance of
image
  1. 由于泛型擦除導(dǎo)致判斷類名一致等(詳情看底部類型擦除的詳情介紹)
  2. 可以定義泛型數(shù)組,但不能給泛型數(shù)組初始化
  3. 泛型類不能繼承Exception或Throwable惧浴,不能捕獲泛型類對象
image
  1. 泛型類型變量不能是基本數(shù)據(jù)類型 比如GenericType<double>是不行的

五存和、泛型類型的繼承規(guī)則和通配符類型

定義三個類:子類Apple繼承父類Fruit Fruit類繼承Food 三個類是這樣一個關(guān)系

public class GenericType<T> {

    private T mData;
    //省略 get set方法
}

盡管Apple繼承父類Fruit ,但注意 GenericType<Apple> 和 GenericType<Fruit> 之間沒有繼承關(guān)系衷旅。所以如果想讓其有繼承關(guān)系哑姚,引入--通配符

通配符--使用方法時定義

//apple-fruit-food
GenericType<Fruit> fruitGenericType = new GenericType<>();
GenericType<Apple> appleGenericType = new GenericType<>();
GenericType<Food> foodGenericType=new GenericType<>();
print(appleGenericType);//不可以,因?yàn)闆]有繼承關(guān)系
print2(appleGenericType);//可以 因?yàn)?? extends Fruit  限定Fruit的子類都可以
print2(foodGenericType);//不可以 food是fruit的父類
print3(foodGenericType);//可以 ? super Fruit 限定Fruit的父類都可以

public static void print(GenericType<Fruit> type) {
}

public static void print2(GenericType<? extends Fruit> type) {
}

public static void print3(GenericType<? super Fruit> type) {
}

extend --規(guī)定了傳入?yún)?shù)的訪問上限 主要用于安全的“訪問”數(shù)據(jù)芜茵。不能set

GenericType<? extends Fruit> type=new GeneicType<>();
type.setData();// 不可以
Fruit fruit=type.getData();//可以
GenericType<? super Fruit> type2=new GenericType<>();
type.setData(new Fruit());//可
type.setData(new Apply());//可
type.setData(new Food())//不可以 需要限定Fruit的子類! 
type.getData();//可以 但返回類型只能是Object

super --規(guī)定了傳入?yún)?shù)的下限,主要用于安全的寫入數(shù)據(jù)叙量,寫入數(shù)據(jù)限定在x的子類

六、虛擬機(jī)是如何實(shí)現(xiàn)泛型的九串?-類型擦除

  1. 在編譯時绞佩,類型擦除,會用一個原生類型代替泛型T

比如<T> 會將定義的T替換成Object

但如果<T extends ArrayList> 將T替換為ArrayList

即有限定類型用限定類型(第一個邊界)替換猪钮,無限定類型用Object替換

生成字節(jié)碼時品山,里面是不包含泛型具體對象的,比如List<String> List<Integer>都要被轉(zhuǎn)化為List

public class GenericType<T>{
    private T mData;
}
//查看.class字節(jié)碼文件為
public class GenericType<Object>{
    private Object mData;
}
  1. 在調(diào)用泛型方法時烤低,可以指定泛型肘交,也可以不指定泛型
  • 在不指定泛型的情況下,泛型變量的類型為該方法中的幾種類型的同一父類的最小級扑馁,直到Object
  • 在指定泛型的情況下涯呻,該方法的幾種類型必須是該泛型的實(shí)例的類型或者其子類
public class Test {  
    public static void main(String[] args) {  

        /**不指定泛型的時候*/  
        int i = Test.add(1, 2); //這兩個參數(shù)都是Integer凉驻,所以T為Integer類型  
        Number f = Test.add(1, 1.2); //這兩個參數(shù)一個是Integer,一個是Float复罐,所以取同一父類的最小級涝登,為Number  
        Object o = Test.add(1, "asd"); //這兩個參數(shù)一個是Integer,一個是String效诅,所以取同一父類的最小級胀滚,為Object  

        /**指定泛型的時候*/  
        int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能為Integer類型或者其子類  
        int b = Test.<Integer>add(1, 2.2); //編譯錯誤乱投,指定了Integer咽笼,不能為Float  
        Number c = Test.<Number>add(1, 2.2); //指定為Number,所以可以為Integer和Float  
    }  

    //這是一個簡單的泛型方法  
    public static <T> T add(T x,T y){  
        return y;  
    }  
}
  1. 證明泛型被擦除:

驗(yàn)證1:

ImplGeneric<String> stringImplGeneric = new ImplGeneric<>();
ImplGeneric<Integer> integerImplGeneric = new ImplGeneric<>();
final Class stringImplGenericClass = stringImplGeneric.getClass();
final Class integerImplGenericClass = integerImplGeneric.getClass();
boolean equal = stringImplGenericClass == integerImplGenericClass;
System.out.println(equal);
輸出true

ImplGeneric<String> 和ImplGeneric<Integer>打印其class文件 發(fā)現(xiàn)相同戚炫,如果打印其className,最終都是ImplGeneric類

驗(yàn)證2:

image

重載--需要保證方法名相同褐荷,參數(shù)不同,但以上圖來看嘹悼,說明List<String> 和 List<Integer> 相同叛甫,因?yàn)轭愋筒脸脸蠖际荓ist,所以編譯器編譯不通過的杨伙。所以不符合重載其监,編譯不通過。

驗(yàn)證3:

  ArrayList<Integer> list = new ArrayList<Integer>();
  list.add(1);  //這樣調(diào)用 add 方法只能存儲整形限匣,因?yàn)榉盒皖愋偷膶?shí)例為 Integer
  list.getClass().getMethod("add", Object.class).invoke(list, "asd");
  for (int i = 0; i < list.size(); i++) {
      System.out.println(list.get(i));
  }

在程序中定義了一個ArrayList泛型類型實(shí)例化為Integer對象抖苦,如果直接調(diào)用add()方法,那么只能存儲整數(shù)數(shù)據(jù)米死,不過當(dāng)我們利用反射調(diào)用add()方法的時候锌历,卻可以存儲字符串,這說明了Integer泛型實(shí)例在編譯之后被擦除掉了峦筒,只保留了原始類型究西。

七、類型擦除與多態(tài)的沖突和解決方法

父類

public class GenericType<T> {

    private T mData;
    
    public T getData() {
        return mData;
    }

    public void setData(T data) {
        mData = data;
    }
}

子類

public class IntegerType extends GenericType<Integer> {

    private Integer mData;

    @Override
    public Integer getData() {
        return 100;
    }

    @Override
    public void setData(Integer data) {
        mData = data;
    }
}

看起來是正常的物喷,子類限定泛型類型為Integer卤材,重寫了get set方法

但要知道父類類型擦除后原生類型Object代替了T那么將變成

public class GenericType<Object> {

    private Object mData;

    public Object getData() {
        return mData;
    }

    public void setData(Object data) {
        mData = data;
    }
}

父類的setData(Object data) 子類為setData(Integer data),這樣看并不符合重寫規(guī)則峦失,因?yàn)橹貙懯且蟾割惡妥宇惙椒▍?shù)一致的扇丛?類型擦除和多態(tài)特性有了沖突:

本意是將IntegerType變?yōu)檫@樣

public class IntegerType {

    private Integer mData;

    @Override
    public Integer getData() {
        return 100;
    }

    @Override
    public void setData(Integer data) {
        mData = data;
    }
}

正常編譯器做不到,只能變?yōu)镺bject尉辑。但為了實(shí)現(xiàn)這個需求帆精,JVM做了特殊優(yōu)化,通過使用橋方法。

反編譯IntegerType.class的字節(jié)碼文件(javap -c IntegerType.class 命令 )卓练,結(jié)果如下:

public class IntegerType extends GenericType<java.lang.Integer> {
  public com.study.java.generic.IntegerType();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method com/study/java/generic/GenericType."<init>":()V
       4: return

  public java.lang.Integer getData();
    Code:
       0: bipush        100
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: areturn

  public void setData(java.lang.Integer);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #3                  // Field mData:Ljava/lang/Integer;
       5: return

  public void setData(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: checkcast     #4                  // class java/lang/Integer
       5: invokevirtual #5                  // Method setData:(Ljava/lang/Integer;)V
       8: return

  public java.lang.Object getData();
    Code:
       0: aload_0
       1: invokevirtual #6                  // Method getData:()Ljava/lang/Integer;
       4: areturn
}

可以發(fā)現(xiàn)除了我們已知的setData(java.lang.Integer)和java.lang.Integer getData()還有兩個JVM生成的兩個橋方法setData(java.lang.Object)和java.lang.Object getData()隘蝎。在setData(java.lang.Object)里第25行,實(shí)際調(diào)用的是setData(java.lang.Integer) 在java.lang.Object getData()里的第32行昆庇,實(shí)際調(diào)用的Integer getData()。這就是橋方法闸溃。橋方法的內(nèi)部實(shí)現(xiàn)整吆,就只是去調(diào)用我們自己重寫的那兩個方法。

虛擬機(jī)巧妙的使用了橋方法辉川,來解決了類型擦除和多態(tài)的沖突表蝙。

這時候又會有一個疑問炉菲,如圖編譯器提示蕴侧,在一個類中,已經(jīng)重寫了getData[返回值為Integer]泳赋,此時再加一個getData[返回值為Object]屿愚,在常規(guī)編程中是不允許的汇跨,不能通過編譯器檢查的。因?yàn)樵诰幾g時我們判斷一個方法是否相同主要是判斷方法名和參數(shù)妆距,但穷遂!虛擬機(jī)內(nèi)部判斷方法是否相同是判斷方法名 參數(shù),外加返回值娱据。所以編譯器為了實(shí)現(xiàn)泛型的多態(tài)允許自己做這個看起來“不合法”的事情蚪黑,然后交給虛擬器去區(qū)別。

image

八中剩、其他

  1. 既然說類型變量會在編譯的時候擦除掉忌穿,那為什么我們往 ArrayList<String>添加int值會錯誤呢?

    Java編譯器是通過先檢查代碼中泛型的類型,然后在進(jìn)行類型擦除结啼,再進(jìn)行編譯掠剑。

  2. 既然都被替換為原始類型,那么為什么我們在獲取的時候郊愧,不需要進(jìn)行強(qiáng)制類型轉(zhuǎn)換呢澡腾?

//看一下ArrayList.get()方法
public E get(int index) {  

    RangeCheck(index);  

    return (E) elementData[index];  

}

在獲取時會根據(jù)泛型類型做一個強(qiáng)制類型轉(zhuǎn)化

  1. 注意,在虛擬機(jī)里糕珊,泛型信息雖然擦除动分,但會保留在Signature。
  2. 為什么要擦除红选?jdk1.5之后加入的泛型澜公,為了兼容之前的版本,才要做擦除。
  3. .......有遇到的新知識點(diǎn)繼續(xù)補(bǔ)充
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坟乾,一起剝皮案震驚了整個濱河市迹辐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌甚侣,老刑警劉巖明吩,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異殷费,居然都是意外死亡印荔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門详羡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仍律,“玉大人,你說我怎么就攤上這事实柠∷” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵窒盐,是天一觀的道長草则。 經(jīng)常有香客問我,道長蟹漓,這世上最難降的妖魔是什么畔师? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮牧牢,結(jié)果婚禮上看锉,老公的妹妹穿的比我還像新娘。我一直安慰自己塔鳍,他們只是感情好伯铣,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著轮纫,像睡著了一般腔寡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掌唾,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天放前,我揣著相機(jī)與錄音,去河邊找鬼糯彬。 笑死凭语,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撩扒。 我是一名探鬼主播似扔,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了炒辉?” 一聲冷哼從身側(cè)響起豪墅,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黔寇,沒想到半個月后偶器,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缝裤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年屏轰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倘是。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡亭枷,死狀恐怖袭艺,靈堂內(nèi)的尸體忽然破棺而出搀崭,到底是詐尸還是另有隱情,我是刑警寧澤猾编,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布瘤睹,位于F島的核電站,受9級特大地震影響答倡,放射性物質(zhì)發(fā)生泄漏轰传。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一瘪撇、第九天 我趴在偏房一處隱蔽的房頂上張望获茬。 院中可真熱鬧,春花似錦倔既、人聲如沸恕曲。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佩谣。三九已至,卻和暖如春实蓬,著一層夾襖步出監(jiān)牢的瞬間茸俭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工安皱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留调鬓,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓酌伊,卻偏偏與公主長得像袖迎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

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

  • 為什么是object 我們都知道object是所有類的超類,那么在早期沒有泛型概念的時候 其實(shí)就是用object來...
    金色888閱讀 514評論 0 0
  • 回首望來做了程序員也有些年頭了燕锥,一直渾渾噩噩辜贵,從事Android也有三年多了,最近回頭一看自己還是什么都不會...
    暴躁的心閱讀 470評論 1 2
  • 1.泛型簡介 問題:在獲取用戶信息的API中,后臺給我們返回一個這樣形式的json字符串归形。{ "meta":...
    彼岸之城cyy閱讀 963評論 0 0
  • 一托慨、引入泛型機(jī)制的原因 假如我們想要實(shí)現(xiàn)一個String數(shù)組,并且要求它可以動態(tài)改變大小暇榴,這時我們都會想到用Arr...
    Q南南南Q閱讀 537評論 0 1
  • 開發(fā)人員在使用泛型的時候厚棵,很容易根據(jù)自己的直覺而犯一些錯誤。比如一個方法如果接收List作為形式參數(shù)蔼紧,那么如果嘗試...
    時待吾閱讀 1,050評論 0 3