Java中的泛型詳細(xì)介紹

這篇文章主要講解了“Java中的泛型詳細(xì)介紹”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“Java中的泛型詳細(xì)介紹”吧!

信豐網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、自適應(yīng)網(wǎng)站建設(shè)等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營維護(hù)。創(chuàng)新互聯(lián)公司從2013年開始到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)公司。

簡介

泛型的作用就是把類型參數(shù)化,也就是我們常說的類型參數(shù)

平時我們接觸的普通方法的參數(shù),比如public void fun(String s);參數(shù)的類型是String,是固定的

現(xiàn)在泛型的作用就是再將String定義為可變的參數(shù),即定義一個類型參數(shù)T,比如public static <T> void fun(T t);這時參數(shù)的類型就是T的類型,是不固定的

從上面的String和T來看,泛型有著濃濃的多態(tài)的味道,但實(shí)際上泛型跟多態(tài)還是有區(qū)別的

從本質(zhì)上來講,多態(tài)是Java中的一個特性,一個概念,泛型是真實(shí)存在的一種類型;

目錄

下面我們詳細(xì)說下Java中的泛型相關(guān)的知識點(diǎn),目錄如下:

  • 什么是類型參數(shù)

  • 為啥要有泛型

  • 泛型的演變史

  • 類型擦除

  • 泛型的應(yīng)用場景

  • 通配符限定

  • 動態(tài)類型安全

  • 等等

正文中大部分示例都是以集合中的泛型為例來做介紹,因?yàn)橛玫谋容^多,大家都熟悉

正文

什么是類型參數(shù)

類型參數(shù)就是參數(shù)的類型,它接受類作為實(shí)際的值

白話一點(diǎn)來說,就是你可以把類型參數(shù)看作形參,把實(shí)際傳入的類看作實(shí)參

比如:ArrayList<E>中的類型參數(shù)E看做形參, ArrayList<String>中的類String看做實(shí)參

如果你學(xué)過工廠設(shè)計(jì)模式,那么就可以把這里的ArrayList<E>看做一個工廠類,然后你需要什么類型的ArrayList,就傳入對應(yīng)的類型參數(shù)即可

  • 比如,傳入Integer則為ArrayList<Integer>

  • 比如,傳入String則為ArrayList<String>

為啥要有泛型

主要是為了提高代碼可讀性和安全性

具體的要從泛型的演變史說起

泛型的演變史

從廣義上來說,泛型很早就有了,只是隱式存在的;

比如List list = new ArrayList(); //等價于List<Object> list = new ArrayList<>();

但是這個時候的泛型是很脆弱的,可讀性和安全性都很差(這個時期的集合相對于數(shù)組來說,優(yōu)勢還不是很大)

首先,填充數(shù)據(jù)時,沒有類型檢查,那就有可能把Cat放到Dog集合中

其次,取出時,需要類型轉(zhuǎn)換,如果你很幸運(yùn)的把對象放錯了集合(有可能是故意的),那么運(yùn)行時就會報錯轉(zhuǎn)換異常(但是編譯卻可以通過)

不過到了JDK1.5,出現(xiàn)了真正意義上的泛型(類型參數(shù),用尖括號<>表示);

比如List<E>集合類,其中的E就是泛型的類型參數(shù),因?yàn)榧现卸际谴娴脑谽lement,所以用E字母替代(類似還有T,S,K-key,V-value);

這個時候,程序的健壯性就提高了,可讀性和安全性也都很高,看一眼就知道放進(jìn)去的是個啥東西(這個時期的集合相對于數(shù)組來說,優(yōu)勢就很明顯了

現(xiàn)在拿List<Dog> list = new ArrayList<>();來舉例說明

首先,填充數(shù)據(jù)時,編譯器自己會進(jìn)行類型檢查,防止將Cat放入Dog中

其次,取出數(shù)據(jù)時,不需要我們手動進(jìn)行類型轉(zhuǎn)換,編譯器自己會進(jìn)行類型轉(zhuǎn)換

細(xì)心的你可能發(fā)現(xiàn)了,既然有了泛型,那我放進(jìn)去的是Dog,取出的不應(yīng)該也是Dog嗎?為啥編譯器還要類型轉(zhuǎn)換呢?

這里就引出類型擦除的概念

類型擦除

什么是類型擦除?

類型擦除指的是,你在給類型參數(shù)<T>賦值時,編譯器會將實(shí)參類型擦除為Object(這里假設(shè)沒有限定符,限定符下面會講到)

所以這里我們要明白一個東西:虛擬機(jī)中沒有泛型類型對象的概念,在它眼里所有對象都是普通對象

比如下面的代碼

擦除前

public class EraseDemo<T> {
    private T t;
    public static void main(String[] args) {
        
    }
    public T getT(){
        return t;
    }
    public void setT(T t){
        this.t = t;
    }
}

擦除后

public class EraseDemo {
    private Object t;
    public static void main(String[] args) {
        
    }
    public Object getT(){
        return t;
    }
    public void setT(Object t){
        this.t = t;
    }
}

可以看到,T都變成了Object

泛型類被擦除后的類型,我們一般叫它原始類型(raw type),比如EraseDemo<T>擦除后的原始類型就是EraseDemo

相應(yīng)的,如果你有兩個數(shù)組列表,ArrayList<String>ArrayList<Integer> ,編譯器也會把兩者都擦除為ArrayList

你可以通過代碼來測試一下

ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass());// 這里會打印true

上面提到的限定符是干嘛的?

限定符就是用來限定邊界的,如果泛型有設(shè)置邊界,比如<T extends Animal>,那么擦除時,會擦除到第一個邊界Animal類,而不是Object

下面還是以上面的代碼為例,展示下擦除前后的對比

擦除前:

public class EraseDemo<T extends Animal> {
    private T t;
    public static void main(String[] args) {
        
    }
    public T getT(){
        return t;
    }
    public void setT(T t){
        this.t = t;
    }
}

擦除后:

public class EraseDemo {
    private Animal t;
    public static void main(String[] args) {
        
    }
    public Animal getT(){
        return t;
    }
    public void setT(Animal t){
        this.t = t;
    }
}

這里的extends符號是表示繼承的意思嗎?

不是的,這里的extends只是表示前者是后者的一個子類,可以繼承也可以實(shí)現(xiàn)

之所以用extends只是因?yàn)檫@個關(guān)鍵詞已經(jīng)內(nèi)置在Java中了,且比較符合情景

如果自己再造一個關(guān)鍵詞,比如sub,可能會使得某些舊代碼產(chǎn)生問題(比如使用sub作為變量的代碼)

為什么要擦除呢?

這其實(shí)不是想不想擦除的問題,而是不得不擦除的問題

因?yàn)榕f代碼是沒有泛型概念的,這里的擦除主要是為了兼容舊代碼,使得舊代碼和新代碼可以互相調(diào)用

泛型的應(yīng)用場景

  • 從大的方向來說:

    • 用在類中:叫做泛型類,類名后面緊跟<類型參數(shù)>,比如ArrayList<E>

    • 用在方法中:叫做泛型方法,方法的返回值前面添加<類型參數(shù)>,比如:public <T> void fun(T obj)

是不是想到了抽象類和抽象方法?

還是有區(qū)別的,抽象類和抽象方法是相互關(guān)聯(lián)的,但是泛型類和泛型方法之間沒有聯(lián)系

  • 集中到類的方向來說:泛型多用在集合類中,比如ArrayList<E>

如果是自定義泛型的話,推薦用泛型方法,原因有二:

  1. 脫離泛型類單獨(dú)使用,使代碼更加清晰(不用為了某個小功能而泛化整個類)

  2. 泛型類中,靜態(tài)方法無法使用類型參數(shù);但是靜態(tài)的泛型方法可以

通配符限定

這里主要介紹<T>, <? extends T>, <? super T>的區(qū)別

  • <T>:這個是最常用的,就是普通的類型參數(shù),在調(diào)用時傳入實(shí)際的類來替換T即可,這個實(shí)際的類可以是T,也可以是T的子類

比如List<String> list = new ArrayList<>();,這里的String就是實(shí)際傳入的類,用來替換類型參數(shù)T

  • <? extends T>:這個屬于通配符限定中的子類型限定,即傳入實(shí)際的類必須是T或者T子類

乍一看,這個有點(diǎn)像<T>類型參數(shù),都是往里放T或者T的子類;

但是區(qū)別還是挺多的,后面會列出

  • <? super T>:這個屬于通配符限定中的超類型限定,即傳入實(shí)際的類必須是T或者T的父類

  • <?>:這個屬于無限定通配符,即它也不知道里面該放啥類型,所以干脆就不讓你往里添加,只能獲?。ㄟ@一點(diǎn)類似<? extends T>

下面用表格列出<T>,<? extends T>, <? super T>的幾個比較明細(xì)的區(qū)別


<T><? extends T><? super T>
類型擦除傳入實(shí)參時,實(shí)參被擦為Object,但是在get時編譯器會自動轉(zhuǎn)為T擦到T擦到Object
引用對象不能將引用指向子類型或者父類型的對象,比如:List<Animal> list = new ArrayList<Cat>();//報錯能將引用指向子類型的對象,比如:List<? extends Animal> list = new ArrayList<Cat>();能將引用指向父類型的對象,比如:List<? super Cat> list = new ArrayList<Animal>();
添加數(shù)據(jù)可以添加數(shù)據(jù),T或者T的子類不能能,T或者T的子類

下面我們用代碼來演示下

類型擦除:

// <T>類型,傳入實(shí)參時,擦除為Object,但是get時還是實(shí)參的類型
List<Animal> list1 = new ArrayList<>();// 合法
list1.add(new Dog());// 合法
Animal animal = list1.get(0); // 這里不需要強(qiáng)轉(zhuǎn),雖然前面?zhèn)魅雽?shí)參時被擦除為Object,但是get時編譯器內(nèi)部已經(jīng)做了強(qiáng)制類型轉(zhuǎn)換

// <? extends T> 子類型的通配符限定,擦除到T(整個過程不再變)
List<? extends Animal> list2 = list1;// 合法
Animal animal2 = list2.get(0); // 這里不需要強(qiáng)轉(zhuǎn),因?yàn)橹徊脸絋(即Animal)

// <? super T> 超類型的通配符限定,擦除到Object
List<? super Animal> list3 = list1; // 合法
Animal animal3 = (Animal)list3.get(0); // 需要手動強(qiáng)制,因?yàn)楸徊脸絆bject

將引用指向子類型或父類型的對象:

// <T>類型,不能指向子類型或父類型
List<Animal> list = new ArrayList<Dog>();// 報錯:需要的是List<Animal>,提供的是ArrayList<Dog>

// <? extends T> 子類型的通配符限定,指向子類型
List<? extends Animal> list2 = new ArrayList<Dog>();// 合法

// <? super T> 超類型的通配符限定,指向父類型
List<? super Dog> list3 = new ArrayList<Animal>(); // 合法

添加數(shù)據(jù)

// <T>類型,可以添加T或者T的子類型
List<Animal> list1 = new ArrayList<>();
list.add(new Dog());// 合法

// <? extends T> 子類型的通配符限定,不能添加元素
List<? extends Animal> list2 = new ArrayList<Dog>();// 正確
list2.add(new Dog()); // 報錯:不能往里添加元素

// <? super T> 超類型的通配符限定,可以添加T或者T的子類型
List<? super Dog> list3 = new ArrayList<Animal>();
list3.add(new Dog()); // 合法,可以添加T類型的元素
list3.add(new Animal());//報錯,不能添加父類型的元素

下面針對上面的測試結(jié)果進(jìn)行解惑

先從<T>的報錯開始吧

為啥<T>類型的引用不能指向子類型,比如 List<Animal> list = new ArrayList<Dog>();

首先說明一點(diǎn),Animal和Dog雖然是父子關(guān)系(Dog繼承Animal),但是List<Animal> List<Dog>之間是沒有任何關(guān)系的(有點(diǎn)像Java和Javascript)

他們之間的關(guān)系如下圖

Java中的泛型詳細(xì)介紹

之所以這樣設(shè)計(jì),主要是為了類型安全的考慮

下面用代碼演示,假設(shè)可以將List<Animal>指向子類List<Dog>

List<Animal> list = new ArrayList<Dog>();// 假設(shè)這里不報錯
list.add(new Cat()); //這里把貓放到狗里面了

第二行可以看到,很明顯,把貓放到狗里面是不對的,這就又回到了泛型真正出現(xiàn)之前的時期了(沒有泛型,集合存取數(shù)據(jù)時不安全)

那為啥<? extends T>就能指向子類型呢?比如List<? extends Animal> list = new ArrayList<Dog>();

說的淺一點(diǎn),原因是:這個通配符限定出現(xiàn)的目的就是為了解決上面的不能指向子類的問題

當(dāng)然,這個原因說了跟沒說一樣。下面開始正經(jīng)點(diǎn)解釋吧

因?yàn)檫@個通配符限定不允許插入任何數(shù)據(jù),所以當(dāng)你指向子類型時,這個list就只能存放指向的那個集合里的數(shù)據(jù)了,而不能再往里添加;

自然的也就類型安全了,只能訪問,不能添加

為什么<? extends T>不允許插入數(shù)據(jù)呢?

其實(shí)這個的原因跟上面的修改引用對象是相輔相成的,合起來就是為了保證泛型的類型安全性

考慮下面的代碼

List<Animal> list = new ArrayList<>();
list.add(new Cat());
list.add(new Dog());
Dog d = (Dog) list.get(0); // 報錯,轉(zhuǎn)換異常

可以看到,插入的子類很混亂,導(dǎo)致提取時轉(zhuǎn)型容易出錯(這是泛型<T>的一個弊端,當(dāng)然我們寫的時候多用點(diǎn)心可能就不會這個問題)

但是有了<? extends T>之后,就不一樣了

首先你可以通過修改引用的對象來使得list指向不同的Animal子類

其次你添加數(shù)據(jù),不能直接添加,但是可以通過指向的Animal子類對象來添加

這樣就保證了類型的安全性

代碼如下:

// 定義一個Dog集合
List<Dog> listDog = new ArrayList<>();
listDog.add(new Dog());

// 讓<? extends Animal>通配符限定的泛型 指向上面的Dog集合
List<? extends Animal> list2 = listDog;
// 這時如果想往里添加數(shù)據(jù),只需要操作listDog即可,它可以保證類型安全
listDog.add(new Dog());
// 如果自己去添加,就會報錯
list2.add(new Dog());// 報錯

<? extends T>一般用在形參,這樣我們需要哪個子類型,只需要傳入對應(yīng)子類的泛型對象就可以了,從而實(shí)現(xiàn)泛型中的多態(tài)

<? super T>為啥可以插入呢?

兩個原因

  1. 它只能插入T或者T的子類

  2. 它的下限是T

也就是說你隨便插入,我已經(jīng)限制了你插入的類型為T或者T的子類

那么我在查詢時,就可以放心的轉(zhuǎn)為T或者T的父類

代碼如下:

List<? super Dog> listDog = new ArrayList<>();
listDog.add(new Dog());
listDog.add(new Cat()); // 報錯
listDog.add(new Animal()); // 報錯
Dog dog = (Dog) listDog.get(0); // 內(nèi)部被擦除為Object,所以要手動強(qiáng)轉(zhuǎn)

為啥<T>獲取時,編譯器會自動強(qiáng)轉(zhuǎn)轉(zhuǎn)換,到了這里<? super T>,就要手動轉(zhuǎn)換了呢?

這個可能是因?yàn)榫幾g器也不確定你的要返回的T的父類是什么類型,所以干脆留給你自己來處理了

但是如果你把這個listDog指向一個父類的泛型對象,然后又在父類的泛型對象中,插入其他類型,那可就亂了(又回到<T>的問題了,要自己多注意)

比如:

List<Animal> list = new ArrayList<>();
list.add(new Cat()); // 加了Cat
// 指向Animal
List<? super Dog> listDog = list;
listDog.add(new Dog());
list.add(new Cat()); // 報錯
list.add(new Animal()); // 報錯

Dog dog = (Dog) listDog.get(0); //報錯:轉(zhuǎn)換異常Cat-》Dog

所以建議<? super T>在添加數(shù)據(jù)的時候,盡量集中在一個地方,不要多個地方添加,像上面的,要么都在<? super Dog>里添加數(shù)據(jù),要么都在<Animal>中添加

動態(tài)類型安全檢查

這個主要是為了跟舊代碼兼容,對舊代碼進(jìn)行的一種類型安全檢查,防止將Cat插入Dog集合中這種錯誤

這種檢查是發(fā)生在編譯階段,這樣就可以提早發(fā)現(xiàn)問題

對應(yīng)的類為Collections工具類,方法如下圖

Java中的泛型詳細(xì)介紹

代碼如下

// 動態(tài)類型安全檢查,在與舊代碼兼容時,防止將Dog放到Cat集合中類似的問題

// === 檢查之前 ===
List list = new ArrayList<Integer>();
// 添加不報錯
list.add("a");
list.add(1);
// 只有用的時候,才會報錯
Integer i = (Integer) list.get(0); // 這里運(yùn)行時報錯

// === 檢查之后 ===
List list2 = Collections.checkedList(new ArrayList<>(), Integer.class);
// 插入時就會報錯
list2.add("a"); // 這里編譯時就報錯,提前發(fā)現(xiàn)錯誤
list2.add(1);

總結(jié)

泛型的作用:

  1. 提高類型安全性:預(yù)防各種類型轉(zhuǎn)換問題

  2. 增加程序可讀性:所見即所得,看得到放進(jìn)去的是啥,也知道會取出啥

  3. 提高代碼重用性:多種同類型的數(shù)據(jù)(比如Animal下的Dog,Cat)可以集合到一處來處理,從而調(diào)高代碼重用性

類型擦除:

泛型T在傳入實(shí)參時,實(shí)參的類型會被擦除為限定類型(即<? extends T>中的T),如果沒有限定類型,則默認(rèn)為Object

通配符限定:

  1. <? extends T>:子類型的通配符限定,以查詢?yōu)橹?/strong>,比如消費(fèi)者集合場景

  2. <? super T>:超類型的通配符限定,以添加為主,比如生產(chǎn)者集合場景

感謝各位的閱讀,以上就是“Java中的泛型詳細(xì)介紹”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對Java中的泛型詳細(xì)介紹這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!

當(dāng)前標(biāo)題:Java中的泛型詳細(xì)介紹
轉(zhuǎn)載注明:http://bm7419.com/article46/pcsehg.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供搜索引擎優(yōu)化、靜態(tài)網(wǎng)站品牌網(wǎng)站建設(shè)、App開發(fā)電子商務(wù)、響應(yīng)式網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)