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