java泛型的知識(shí)整理

為什么我們需要泛型坐求?

通過(guò)兩段代碼我們就可以知道為何我們需要泛型


同操作 不同參數(shù)類型的方法

實(shí)際開(kāi)發(fā)中,經(jīng)常有數(shù)值類型求和的需求晌梨,例如實(shí)現(xiàn)int類型的加法, 有時(shí)候還需要實(shí)現(xiàn)long類型的求和, 如果還需要double類型的求和桥嗤,需要重新在重載一個(gè)輸入是double類型的add方法。這樣方法就顯得多余和重復(fù)了仔蝌。

  • 定義一個(gè)List類型的集合泛领,先向其中加入了兩個(gè)字符串類型的值,隨后加入一個(gè)Integer類型的值敛惊。這是完全允許的渊鞋,因?yàn)榇藭r(shí)list默認(rèn)的類型為Object類型。在之后的循環(huán)中豆混,由于忘記了之前在list中也加入了Integer類型的值或其他編碼原因篓像,很容易出現(xiàn)類似于//上圖中的錯(cuò)誤。因?yàn)榫幾g階段正常皿伺,而運(yùn)行時(shí)會(huì)出現(xiàn)“java.lang.ClassCastException”異常员辩。因?yàn)閷?dǎo)致此類錯(cuò)誤編碼過(guò)程中不易發(fā)現(xiàn)。
    類型轉(zhuǎn)換異常

    在如上的編碼過(guò)程中鸵鸥,我們發(fā)現(xiàn)主要存在兩個(gè)問(wèn)題
  1. 當(dāng)我們將一個(gè)對(duì)象放入集合中奠滑,集合不會(huì)記住此對(duì)象的類型丹皱,當(dāng)再次從集合中取出此對(duì)象時(shí),該對(duì)象的編譯類型變成了Object類型宋税,但其運(yùn)行時(shí)類型仍然為其本身類型摊崭。
  2. 因此,//上圖 取出集合元素時(shí)需要人為的強(qiáng)制類型轉(zhuǎn)化到具體的目標(biāo)類型杰赛,且很容易出現(xiàn)“java.lang.ClassCastException”異常呢簸。所以泛型的好處就是:
    1)適用于多種數(shù)據(jù)類型執(zhí)行相同的代碼
    2)泛型中的類型在使用時(shí)指定,不需要強(qiáng)制類型轉(zhuǎn)換

泛型類和泛型接口

泛型乏屯,即“參數(shù)化類型”根时。一提到參數(shù)拯啦,最熟悉的就是定義方法時(shí)有形參循头,然后調(diào)用此方法時(shí)傳遞實(shí)參憎瘸。那么參數(shù)化類型怎么理解呢肺稀?
顧名思義,就是將類型由原來(lái)的具體的類型參數(shù)化改执,類似于方法中的變量參數(shù)体谒,此時(shí)類型也定義成參數(shù)形式(可以稱之為類型形參)填抬,然后在使用/調(diào)用時(shí)傳入具體的類型(類型實(shí)參)窘问。
泛型的本質(zhì)是為了參數(shù)化類型(在不創(chuàng)建新的類型的情況下辆童,通過(guò)泛型指定的不同類型來(lái)控制形參具體限制的類型)。也就是說(shuō)在泛型使用過(guò)程中南缓,操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)胸遇,這種參數(shù)類型可以用在類、接口和方法中汉形,分別被稱為泛型類、泛型接口倍阐、泛型方法概疆。
引入一個(gè)類型變量T(其他大寫字母都可以,不過(guò)常用的就是T峰搪,E岔冀,K,V等等)概耻,并且用<>括起來(lái)使套,并放在類名的后面。泛型類是允許有多個(gè)類型變量的鞠柄。

public class GenericClass<T> {
    private T data;
    public T getData() {
        return data;
    }

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

public class GenericClass2<T,K> {
    private T data;
    private K key;
    public GenericClass(T data, K key) {
        this.data = data;
        this.key = key;
    }
}

泛型接口與泛型類的定義基本相同侦高。

public interface GenericInterface<T> {
    T onNext();
}

而實(shí)現(xiàn)泛型接口的類,有兩種實(shí)現(xiàn)方法:
1厌杜、未傳入泛型實(shí)參時(shí):

public class ImplGenericInterface<T> implements GenericInterface<T> {
    @Override
    public T onNext() {
        return null;
    }
}

在new出類的實(shí)例時(shí)奉呛,需要指定具體類型:

ImplGenericInterface<String> implGenericInterface = new ImplGenericInterface<>();

2计螺、傳入泛型實(shí)參

public class ImplGenericInterface2 implements GenericInterface<String> {
    @Override
    public String onNext() {
        return null;
    }
}

在new出類的實(shí)例時(shí),和普通的類沒(méi)區(qū)別瞧壮。

泛型方法

/**
     * @param <T> 泛型方法的特定寫法
     * @return 返回參數(shù)類型是傳入的參數(shù)類型
     */
    private <T> T getMiddleValue(T... a){
        return a[a.length/2];
    }

泛型方法登馒,是在調(diào)用方法的時(shí)候指明泛型的具體類型 ,泛型方法可以在任何地方和任何場(chǎng)景中使用咆槽,包括普通類和泛型類陈轿。注意泛型類中定義的普通方法和泛型方法的區(qū)別
普通方法:

public class GenericClass<T> {
    private T data;
    /**
     * 這個(gè)方法中雖然使用了泛型作為返回參數(shù)類型
     * 但是這個(gè)方法只是 GenericClass 類的普通成員方法
     * 在權(quán)限修飾符 和 返回參數(shù)類型之間還有一個(gè) <T> 才是泛型方法的標(biāo)示(T和定義的泛型表達(dá)一致)
     */
    public T getData() {
        return data;
    }
}

泛型方法

public class GenericClass<T> {
    private T data;
    /**
     * 這個(gè)方法中雖然使用了泛型作為返回參數(shù)類型
     * 但是這個(gè)方法只是 GenericClass 類的普通成員方法
     * 在權(quán)限修飾符 和 返回參數(shù)類型之間還有一個(gè) <T> 才是泛型方法的標(biāo)示(T和定義的泛型表達(dá)一致)
     */
    public T getData() {
        return data;
    }

    /**
     * 泛型方法 和類中的泛型 無(wú)關(guān)聯(lián)
     * 有泛型方法的特定標(biāo)示<E>
     */
    public <E> E testGeneric(E e){
        //對(duì)E 進(jìn)行相應(yīng)的操作
        return e;
    }
}

限定類型變量

有時(shí)候,我們需要對(duì)類型變量加以約束秦忿,比如計(jì)算兩個(gè)變量的最小济欢,最大值。


在這里插入圖片描述

請(qǐng)問(wèn)小渊,如果確保傳入的兩個(gè)變量一定有compareTo方法法褥?那么解決這個(gè)問(wèn)題的方案就是將T限制為實(shí)現(xiàn)了接口Comparable的類

 public static <T extends Comparable<T>> T min(@NonNull T a, @NonNull T b){
        if(a.compareTo(b) > 0){
            return b;
        }else {
            return a;
        }
    }

T extends Comparable中
T表示應(yīng)該綁定類型的子類型,Comparable表示綁定類型酬屉,子類型和綁定類型可以是類也可以是接口半等。
如果這個(gè)時(shí)候,我們?cè)噲D傳入一個(gè)沒(méi)有實(shí)現(xiàn)接口Comparable的類的實(shí)例呐萨,將會(huì)發(fā)生編譯錯(cuò)誤杀饵。

在這里插入圖片描述

同時(shí)extends左右都允許有多個(gè),如 T,V extends Comparable & Serializable
注意限定類型中谬擦,只允許有一個(gè)類切距,而且如果有類,這個(gè)類必須是限定列表的第一個(gè)惨远。
這種類的限定既可以用在泛型方法上也可以用在泛型類上谜悟。

泛型中的約束和局限性

現(xiàn)在我們有泛型類

public class Restrict<T> {
    private T data;
    public Restrict(T data) {
        this.data = data;
    }
}

不能用基本類型實(shí)例化類型參數(shù)

在這里插入圖片描述

運(yùn)行時(shí)類型查詢只適用于原始類型

在這里插入圖片描述

泛型類的靜態(tài)上下文中類型變量失效

在這里插入圖片描述

不能在靜態(tài)域或方法中引用類型變量。因?yàn)榉盒褪且趯?duì)象創(chuàng)建的時(shí)候才知道是什么類型的北秽,而對(duì)象創(chuàng)建的代碼執(zhí)行先后順序是static的部分葡幸,然后才是構(gòu)造函數(shù)等等。所以在對(duì)象初始化之前static的部分已經(jīng)執(zhí)行了贺氓,如果你在靜態(tài)部分引用的泛型蔚叨,那么毫無(wú)疑問(wèn)虛擬機(jī)根本不知道是什么東西,因?yàn)檫@個(gè)時(shí)候類還沒(méi)有初始化辙培。

不能創(chuàng)建參數(shù)化類型的數(shù)組

   Restrict<Double>[] restrictArray;//可以定義
       Restrict<Double>[] restricts = new Restrict<Double>[10];//不能這樣使用

不能實(shí)例化類型變量

在這里插入圖片描述

不能捕獲泛型類的實(shí)例

在這里插入圖片描述

但是這樣可以:


image

泛型類型的繼承規(guī)則

現(xiàn)在我們有一個(gè)類和子類,有一個(gè)泛型類


在這里插入圖片描述

請(qǐng)問(wèn)Pair<Employee>和Pair<Worker>是繼承關(guān)系嗎蔑水?
答案:不是,他們之間沒(méi)有什么關(guān)系
但是泛型類可以繼承或者擴(kuò)展其他泛型類扬蕊,比如List和ArrayList

Pair<Employee> pair = new ExtentPair<>();
class Pair<T>{
}
class ExtentPair<T> extends Pair<T>{
}

通配符類型

正是因?yàn)榍懊嫠龅牟蟊穑琍air<Employee>和Pair<Worker>沒(méi)有任何關(guān)系,如果我們有一個(gè)泛型類和一個(gè)方法
并有如下使用情況厨相,怎會(huì)產(chǎn)生錯(cuò)誤领曼。


image

為解決這個(gè)問(wèn)題鸥鹉,于是提出了一個(gè)通配符類型 ?
有兩種使用方式:
? extends X 表示類型的上界庶骄,類型參數(shù)是X的子類
毁渗? super X 表示類型的下界,類型參數(shù)是X的超類
這兩種 方式從名字上來(lái)看单刁,特別是super灸异,很有迷惑性,下面我們來(lái)仔細(xì)辨析這兩種方法羔飞。
肺樟? extends X
表示傳遞給方法的參數(shù),必須是X的子類(包括X本身)


在這里插入圖片描述

但是對(duì)泛型類Pair來(lái)說(shuō)逻淌,如果其中提供了get和set類型參數(shù)變量的方法的話么伯,set方法是不允許被調(diào)用的,會(huì)出現(xiàn)編譯錯(cuò)誤
get方法則沒(méi)問(wèn)題卡儒,會(huì)返回一個(gè)Employee類型的值田柔。
為什么呢?

道理很簡(jiǎn)單骨望,硬爆? extends X 表示類型的上界,類型參數(shù)是X的子類擎鸠,那么可以肯定的說(shuō)缀磕,get方法返回的一定是個(gè)X(不管是X或者X的子類)編譯器是可以確定知道的。但是set方法只知道傳入的是個(gè)X劣光,至于具體是X的那個(gè)子類袜蚕,不知道。
總結(jié):主要用于安全地訪問(wèn)數(shù)據(jù)赎线,可以訪問(wèn)X及其子類型廷没,并且不能寫入非null的數(shù)據(jù)。
垂寥? super X
表示傳遞給方法的參數(shù),必須是X的超類(包括X本身)


在這里插入圖片描述

為什么fun1方法不能調(diào)用了另锋,是因?yàn)镋mployee 的超類 Objet 沒(méi)有該方法
改成如下就行了:
private void print(Pair<? super Employee> pair){
        Log.e("YLC",pair.data.toString());
    }

但是對(duì)泛型類Pair來(lái)說(shuō)滞项,如果其中提供了get和set類型參數(shù)變量的方法的話,set方法可以被調(diào)用的夭坪,且能傳入的參數(shù)只能是X或者X的子類文判,get方法只會(huì)返回一個(gè)Object類型的值。
為何室梅?
戏仓? super X 表示類型的下界疚宇,類型參數(shù)是X的超類(包括X本身),那么可以肯定的說(shuō)赏殃,get方法返回的一定是個(gè)X的超類敷待,那么到底是哪個(gè)超類?不知道仁热,但是可以肯定的說(shuō)榜揖,Object一定是它的超類,所以get方法返回Object抗蠢。編譯器是可以確定知道的举哟。對(duì)于set方法來(lái)說(shuō),編譯器不知道它需要的確切類型迅矛,但是X和X的子類可以安全的轉(zhuǎn)型為X妨猩。
總結(jié):主要用于安全地寫入數(shù)據(jù),可以寫入X及其子類型秽褒。

無(wú)限定的通配符 ?

表示對(duì)類型沒(méi)有什么限制壶硅,可以把?看成所有類型的父類震嫉,如Pair< ?>森瘪;
比如:
ArrayList<T> al=new ArrayList<T>(); 指定集合元素只能是T類型
ArrayList<?> al=new ArrayList<?>();集合元素可以是任意類型,這種沒(méi)有意義票堵,一般是方法中扼睬,只是為了說(shuō)明用法。
在使用上:
悴势? getFirst() : 返回值只能賦給 Object窗宇,;
void setFirst(?) : setFirst 方法不能被調(diào)用特纤, 甚至不能用 Object 調(diào)用军俊;

虛擬機(jī)是如何實(shí)現(xiàn)泛型的?

泛型思想早在C++語(yǔ)言的模板(Template)中就開(kāi)始生根發(fā)芽捧存,在Java語(yǔ)言處于還沒(méi)有出現(xiàn)泛型的版本時(shí)粪躬,只能通過(guò)Object是所有類型的父類和類型強(qiáng)制轉(zhuǎn)換兩個(gè)特點(diǎn)的配合來(lái)實(shí)現(xiàn)類型泛化。昔穴,由于Java語(yǔ)言里面所有的類型都繼承于java.lang.Object镰官,所以O(shè)bject轉(zhuǎn)型成任何對(duì)象都是有可能的。但是也因?yàn)橛袩o(wú)限的可能性吗货,就只有程序員和運(yùn)行期的虛擬機(jī)才知道這個(gè)Object到底是個(gè)什么類型的對(duì)象泳唠。在編譯期間,編譯器無(wú)法檢查這個(gè)Object的強(qiáng)制轉(zhuǎn)型是否成功宙搬,如果僅僅依賴程序員去保障這項(xiàng)操作的正確性笨腥,許多ClassCastException的風(fēng)險(xiǎn)就會(huì)轉(zhuǎn)嫁到程序運(yùn)行期之中拓哺。

泛型技術(shù)在C#和Java之中的使用方式看似相同,但實(shí)現(xiàn)上卻有著根本性的分歧脖母,C#里面泛型無(wú)論在程序源碼中士鸥、編譯后的IL中(Intermediate Language,中間語(yǔ)言镶奉,這時(shí)候泛型是一個(gè)占位符)础淤,或是運(yùn)行期的CLR中,都是切實(shí)存在的哨苛,List<int>與List<String>就是兩個(gè)不同的類型鸽凶,它們?cè)谙到y(tǒng)運(yùn)行期生成,有自己的虛方法表和類型數(shù)據(jù)建峭,這種實(shí)現(xiàn)稱為類型膨脹玻侥,基于這種方法實(shí)現(xiàn)的泛型稱為真實(shí)泛型。

Java語(yǔ)言中的泛型則不一樣亿蒸,它只在程序源碼中存在凑兰,在編譯后的字節(jié)碼文件中,就已經(jīng)替換為原來(lái)的原生類型(Raw Type边锁,也稱為裸類型)了姑食,并且在相應(yīng)的地方插入了強(qiáng)制轉(zhuǎn)型代碼,因此茅坛,對(duì)于運(yùn)行期的Java語(yǔ)言來(lái)說(shuō)音半,ArrayList<int>與ArrayList<String>就是同一個(gè)類,所以泛型技術(shù)實(shí)際上是Java語(yǔ)言的一顆語(yǔ)法糖贡蓖,Java語(yǔ)言中的泛型實(shí)現(xiàn)方法稱為類型擦除曹鸠,基于這種方法實(shí)現(xiàn)的泛型稱為偽泛型。

將一段Java代碼編譯成Class文件斥铺,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后彻桃,將會(huì)發(fā)現(xiàn)泛型都不見(jiàn)了,程序又變回了Java泛型出現(xiàn)之前的寫法晾蜘,泛型類型都變回了原生類型

image

上面這段代碼是不能被編譯的邻眷,因?yàn)閰?shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List<E>剔交,擦除動(dòng)作導(dǎo)致這兩種方法的特征簽名變得一模一樣耗溜。

由于Java泛型的引入,各種場(chǎng)景(虛擬機(jī)解析省容、反射等)下的方法調(diào)用都可能對(duì)原有的基礎(chǔ)產(chǎn)生影響和新的需求,如在泛型類中如何獲取傳入的參數(shù)化類型等燎字。因此腥椒,JCP組織對(duì)虛擬機(jī)規(guī)范做出了相應(yīng)的修改阿宅,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來(lái)的參數(shù)類型的識(shí)別問(wèn)題笼蛛,Signature是其中最重要的一項(xiàng)屬性洒放,它的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名[3],這個(gè)屬性中保存的參數(shù)類型并不是原生類型滨砍,而是包括了參數(shù)化類型的信息往湿。修改后的虛擬機(jī)規(guī)范要求所有能識(shí)別49.0以上版本的Class文件的虛擬機(jī)都要能正確地識(shí)別Signature參數(shù)。

另外惋戏,從Signature屬性的出現(xiàn)我們還可以得出結(jié)論领追,擦除法所謂的擦除,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除响逢,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息绒窑,這也是我們能通過(guò)反射手段取得參數(shù)化類型的根本依據(jù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末舔亭,一起剝皮案震驚了整個(gè)濱河市些膨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钦铺,老刑警劉巖订雾,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異矛洞,居然都是意外死亡洼哎,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門缚甩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)谱净,“玉大人,你說(shuō)我怎么就攤上這事擅威『咎剑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵郊丛,是天一觀的道長(zhǎng)李请。 經(jīng)常有香客問(wèn)我,道長(zhǎng)厉熟,這世上最難降的妖魔是什么导盅? 我笑而不...
    開(kāi)封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮揍瑟,結(jié)果婚禮上白翻,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好滤馍,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布岛琼。 她就那樣靜靜地躺著,像睡著了一般巢株。 火紅的嫁衣襯著肌膚如雪槐瑞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天阁苞,我揣著相機(jī)與錄音困檩,去河邊找鬼。 笑死那槽,一個(gè)胖子當(dāng)著我的面吹牛悼沿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播倦炒,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼显沈,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了逢唤?” 一聲冷哼從身側(cè)響起拉讯,我...
    開(kāi)封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鳖藕,沒(méi)想到半個(gè)月后魔慷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡著恩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年院尔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喉誊。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邀摆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出伍茄,到底是詐尸還是另有隱情栋盹,我是刑警寧澤,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布敷矫,位于F島的核電站例获,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏曹仗。R本人自食惡果不足惜榨汤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望怎茫。 院中可真熱鬧收壕,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至端壳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間枪蘑,已是汗流浹背损谦。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岳颇,地道東北人照捡。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像话侧,于是被迫代替她去往敵國(guó)和親栗精。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353