
目錄
前言
1、泛型的概念
2、泛型的使用
3、泛型原理,泛型擦除
3.1 IDEA 查看位元組碼
3.2 泛型擦除原理
4、?和 T 的區別
5、super extends
6、注意點
1、靜態方法無法訪問類的泛型
2、創建之后無法修改型別
3、型別判斷問題
4、創建型別實體
7、總結
前言
泛型是Java中的高級概念,也是構建框架必備技能,比如各種集合類都是泛型實作的,今天詳細聊聊Java中的泛型概念,希望有所識訓,記得點贊,關注,分享哦,
1、泛型的概念
泛型的作用就是把型別引數化,也就是我們常說的型別引數
平時我們接觸的普通方法的引數,比如public void fun(String s);引數的型別是String,是固定的
現在泛型的作用就是再將String定義為可變的引數,即定義一個型別引數T,比如public static <T> void fun(T t);這時引數的型別就是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 查看位元組碼
1、創建Java檔案,并編譯,確認生成了class

2、idea ->選中Java 檔案 ->View

3.2 泛型擦除原理
我們通過例子來看一下,先看一個非泛型的版本:

從位元組碼可以看出,在取出物件的的時候我們做了強制型別轉換,
下面我們給出一個泛型的版本,從位元組碼的角度來看看:

在編譯程序中,型別變數的資訊是能拿到的,所以,set方法在編譯器可以做型別檢查,非法型別不能通過編譯,但是對于get方法,由于擦除機制,運行時的實際參考型別為Object型別,為了“還原”回傳結果的型別,編譯器在get之后添加了型別轉換,所以,在Player.class檔案main方法主體第18行有一處型別轉換的邏輯,它是編譯器自動幫我們加進去的,
所以在泛型類物件讀取和寫入的位置為我們做了處理,為代碼添加約束,
泛型引數將會被擦除到它的第一個邊界(邊界可以有多個,重用 extends 關鍵字,通過它能給與引數型別添加一個邊界),編譯器事實上會把型別引數替換為它的第一個邊界的型別,如果沒有指明邊界,那么型別引數將被擦除到Object,
4、?和 T 的區別
?使用場景 和Object一樣,和C++的Void 指標一樣,基本上就是不確定型別,可以指向任何物件,一般用在參考,
T 是泛型的定義型別,在運行時是確定的型別,
5、super extends
通配符限定:
<? extends T>:子型別的通配符限定,以查詢為主,比如消費者集合場景
<? super T>:超型別的通配符限定,以添加為主,比如生產者集合場景
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 ,
歸根結底可以用一句話表示,那就是編譯器可以支持向上轉型,但不支持向下轉型,具體來講,我可以把Apple物件賦值給Fruit的參考,但是如果把Fruit物件賦值給Apple的參考就必須得用cast,
6、注意點
1、靜態方法無法訪問類的泛型

可以看到Idea 提示無法參考靜態背景關系,
2、創建之后無法修改型別
List<Player> 無法插入其他的型別,已經確定型別的不可以修改型別
3、型別判斷問題
問題:因為型別在編譯完之后無法獲取具體的型別,所以在運行時是無法判斷類的型別,
我們可以通過下面的代碼來解決泛型的型別資訊由于擦除無法進行型別判斷的問題:
/**
* 判斷型別
* @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);
}
}
解決方案:我們通過在創建物件的時候在建構式中傳入具體的class型別,然后通過這個Class物件進行型別判斷,
4、創建型別實體
問題:泛型代碼中不能new T()的原因有兩個,一是因為擦除,不能確定型別;而是無法確定T是否包含無參建構式,
在之前的文章中,有一個需求是根據不同的節點配置實體化創建具體的執行節點,即根據IfNodeCfg 創建具體的IfNode.
/**
* 創建實體
* @author 香菜
*/
public abstract class AbsNodeCfg<T> {
public abstract T getInstance();
}
public class IfNodeCfg extends AbsNodeCfg<IfNode>{
@Override
public IfNode getInstance() {
return new IfNode();
}
}
/**
* 創建實體
* @author 香菜
*/
public class IfNode {
}
解決方案:通過上面的方式可以根據具體的型別,創建具體的實體,擴展的時候直接繼承AbsNodeCfg,并且實作具體的節點就可以了,
7、總結
泛型相當于創建了一組的類,方法,虛擬機中沒有泛型型別物件的概念,在它眼里所有物件都是普通物件

有疑問的可以留言,我們一起討論,沒有問題的也可以留言,我們交個朋友
打字不容易,點贊,轉發,關注三連,謝謝大家支持,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/283223.html
標籤:java
