一篇文章帶你搞定JAVA泛型
1、泛型的概念
泛型的作用就是把類型參數(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的類型,是不固定的
泛型常見的字母有以下:
? 表示不確定的類型 T (type) 表示具體的一個java類型 K V (key value) 分別代表java鍵值中的Key Value E (element) 代表Element
這些字母隨意使用,只是代表類型,也可以用單詞。
2、泛型的使用
泛型有三種使用方式,分別為:泛型類、泛型接口、泛型方法。
類的使用地方是
方法的使用地方
- Java泛型類
- Java泛型方法
- Java泛型接口
/** * @author 香菜 */ public class Player<T> {// 泛型類 private T name; public T getName() { return name; } public void setName(T name) { this.name = name; } }
public class Apple extends Fruit { public <T> void getInstance(T t){// 泛型方法 System.out.println(t); } }
public interface Generator<T> { public T next(); }
3、泛型原理,泛型擦除
3.1 IDEA 查看字節(jié)碼
1、創(chuàng)建Java文件,并編譯,確認(rèn)生成了class
2、idea ->選中Java 文件 ->View
3.2 泛型擦除原理
我們通過例子來看一下,先看一個非泛型的版本:
從字節(jié)碼可以看出,在取出對象的的時候我們做了強(qiáng)制類型轉(zhuǎn)換。
下面我們給出一個泛型的版本,從字節(jié)碼的角度來看看:
在編譯過程中,類型變量的信息是能拿到的。所以,set方法在編譯器可以做類型檢查,非法類型不能通過編譯。但是對于get方法,由于擦除機(jī)制,運(yùn)行時的實(shí)際引用類型為Object類型。為了“還原”返回結(jié)果的類型,編譯器在get之后添加了類型轉(zhuǎn)換。所以,在Player.class文件main方法主體第18行有一處類型轉(zhuǎn)換的邏輯。它是編譯器自動幫我們加進(jìn)去的。
所以在泛型類對象讀取和寫入的位置為我們做了處理,為代碼添加約束。
泛型參數(shù)將會被擦除到它的第一個邊界(邊界可以有多個,重用 extends 關(guān)鍵字,通過它能給與參數(shù)類型添加一個邊界)。編譯器事實(shí)上會把類型參數(shù)替換為它的第一個邊界的類型。如果沒有指明邊界,那么類型參數(shù)將被擦除到Object。
4、?和 T 的區(qū)別
?使用場景 和Object一樣,和C++的Void 指針一樣,基本上就是不確定類型,可以指向任何對象。一般用在引用。
T 是泛型的定義類型,在運(yùn)行時是確定的類型。
5、super extends
通配符限定:
<? extends T>:子類型的通配符限定,以查詢?yōu)橹?/strong>,比如消費(fèi)者集合場景
<? super T>:超類型的通配符限定,以添加為主,比如生產(chǎn)者集合場景
super 下界通配符 ,向下兼容子類及其子孫類, T super Child 會被擦除為 Object
extends 上界通配符 ,向下兼容子類及其子孫類, T extends Parent 會被擦除為 Parent
class Fruit {} class Apple extends Fruit {} class FuShi extends Apple {} class Orange extends Fruit {} import java.util.ArrayList; import java.util.List; public class Aain { public static void main(String[] args) { //上界 List<? extends Fruit> topList = new ArrayList<Apple>(); topList.add(null); //add Fruit對象會報錯 //topList.add(new Fruit()); Fruit fruit1 = topList.get(0); //下界 List<? super Apple> downList = new ArrayList<>(); downList.add(new Apple()); downList.add(new FuShi()); //get Apple對象會報錯 //Apple apple = downList.get(0); }
上界 <? extend Fruit> ,表示所有繼承Fruit的子類,但是具體是哪個子類,但是肯定是Fruit
下界 <? super Apple>,表示Apple的所有父類,包括Fruit,一直可以追溯到老祖宗Object 。
歸根結(jié)底可以用一句話表示,那就是編譯器可以支持向上轉(zhuǎn)型,但不支持向下轉(zhuǎn)型。具體來講,我可以把Apple對象賦值給Fruit的引用,但是如果把Fruit對象賦值給Apple的引用就必須得用cast。
6、注意點(diǎn)
1、靜態(tài)方法無法訪問類的泛型
可以看到Idea 提示無法引用靜態(tài)上下文。
2、創(chuàng)建之后無法修改類型
List<Player> 無法插入其他的類型,已經(jīng)確定類型的不可以修改類型
3、類型判斷問題
問題:因?yàn)轭愋驮诰幾g完之后無法獲取具體的類型,所以在運(yùn)行時是無法判斷類的類型。
我們可以通過下面的代碼來解決泛型的類型信息由于擦除無法進(jìn)行類型判斷的問題:
/** * 判斷類型 * @author 香菜 * @param <T> */ public class GenClass<T> { Class<?> classType; public GenClass(Class<?> classType) { this.classType = classType; } public boolean isInstance(Object object){ return classType.isInstance(object); } }
解決方案:我們通過在創(chuàng)建對象的時候在構(gòu)造函數(shù)中傳入具體的class類型,然后通過這個Class對象進(jìn)行類型判斷。
4、創(chuàng)建類型實(shí)例
問題:泛型代碼中不能new T()的原因有兩個,一是因?yàn)椴脸荒艽_定類型;而是無法確定T是否包含無參構(gòu)造函數(shù)。
在之前的文章中,有一個需求是根據(jù)不同的節(jié)點(diǎn)配置實(shí)例化創(chuàng)建具體的執(zhí)行節(jié)點(diǎn),即根據(jù)IfNodeCfg 創(chuàng)建具體的IfNode.
/** * 創(chuàng)建實(shí)例 * @author 香菜 */ public abstract class AbsNodeCfg<T> { public abstract T getInstance(); }
public class IfNodeCfg extends AbsNodeCfg<IfNode>{ @Override public IfNode getInstance() { return new IfNode(); } }
/** * 創(chuàng)建實(shí)例 * @author 香菜 */ public class IfNode { }
解決方案:通過上面的方式可以根據(jù)具體的類型,創(chuàng)建具體的實(shí)例,擴(kuò)展的時候直接繼承AbsNodeCfg,并且實(shí)現(xiàn)具體的節(jié)點(diǎn)就可以了。
7、總結(jié)
泛型相當(dāng)于創(chuàng)建了一組的類,方法,虛擬機(jī)中沒有泛型類型對象的概念,在它眼里所有對象都是普通對象
本篇文章就到這里了,希望能給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
SpringBoot結(jié)合Tess4J實(shí)現(xiàn)拍圖識字的示例代碼
圖片中的文字提取已經(jīng)越來越多地應(yīng)用于數(shù)據(jù)輸入和自動化處理過程,本文主要介紹了SpringBoot結(jié)合Tess4J實(shí)現(xiàn)拍圖識字的示例代碼,具有一定的參考價值,感興趣的可以了解一下2024-06-06Mybatis下動態(tài)sql中##和$$的區(qū)別講解
今天小編就為大家分享一篇關(guān)于Mybatis下動態(tài)sql中##和$$的區(qū)別講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03SpringBoot實(shí)現(xiàn)自定義線程池的方法
這篇文章主要介紹了SpringBoot中的自定義線程池解析,實(shí)現(xiàn)自定義線程池重寫spring默認(rèn)線程池的方式使用的時候,只需要加@Async注解就可以,不用去聲明線程池類,需要的朋友可以參考下2023-11-11如何開啟控制臺輸出mybatis執(zhí)行的sql日志問題
這篇文章主要介紹了如何開啟控制臺輸出mybatis執(zhí)行的sql日志問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-09-09使用@DS輕松解決動態(tài)數(shù)據(jù)源的問題
這篇文章主要介紹了使用@DS輕松解決動態(tài)數(shù)據(jù)源的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05java調(diào)用chatgpt接口來實(shí)現(xiàn)專屬于自己的人工智能助手
這篇文章主要介紹了用java來調(diào)用chatget的接口,實(shí)現(xiàn)自己的聊天機(jī)器人,對人工智能感興趣的小伙伴可以參考閱讀2023-03-03mybatis-plus指定字段模糊查詢的實(shí)現(xiàn)方法
最近項(xiàng)目中使用springboot+mybatis-plus來實(shí)現(xiàn),所以下面這篇文章主要給大家介紹了關(guān)于mybatis-plus實(shí)現(xiàn)指定字段模糊查詢的相關(guān)資料,需要的朋友可以參考下2022-04-04