Java 泛型(generics)是 JDK1.5 中引入的一個(gè)新特性,其本質(zhì)是參數(shù)化類型,解決不確定具體對(duì)象類型的問題;其所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)(type parameter)這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口、泛型方法。
但是在 Java 中并不是真正的泛型,實(shí)際上是“偽泛型”
類型擦除(type Erasure)
為了與之前的版本兼容,JDK1.5 中通過類型擦除來增加的泛型功能。Java 泛型只是在編譯器層次上,在編譯后生成的字節(jié)碼中是不包含泛型中類型的信息的。
通過一個(gè)例子來證明類型擦除
public class main {
public static void main(String[] args) {
ArrayList<String> sList = new ArrayList<String>();
ArrayList<Integer> iList = new ArrayList<Integer>();
System.out.println(sList.getClass() == iList.getClass());
}
}
上面定義了兩個(gè) ArrayList,一個(gè)是 ArrayList<String>泛型類型的,一個(gè)是 ArrayList<Integer>類型的,但是最后打印的是true,說明兩個(gè)類型相同。
用javap -c看一下生成的生成的字節(jié)碼

可以看到在字節(jié)碼中,ArrayList<String>和 ArrayList<Integer>都被編譯成了 ArrayList 類型,可見編譯后發(fā)生了類型擦除。
- 既然編譯后發(fā)生了類型擦除,那么虛擬機(jī)解析、反射等場(chǎng)景是怎么獲取到正確的類型的?
在 JDk1.5 中增加泛型的同時(shí),JCP 組織修改了虛擬機(jī)規(guī)范,增加了Signature、LocalVariableTypeTable新屬性。
用javap -v查看一下字節(jié)碼,在main方法中包含一段
LocalVariableTypeTable:
Start Length Slot Name Signature
8 31 1 sList Ljava/util/ArrayList<Ljava/lang/String;>;
16 23 2 iList Ljava/util/ArrayList<Ljava/lang/Integer;>;
LocalVariableTypeTable是一個(gè)可選屬性,如果存在泛型,則會(huì)出現(xiàn)這個(gè)屬性。在Signature下包含了泛型的信息。
- 接下來,看這段代碼
ArrayList<String> sList = new ArrayList<String>();
sList.add("111");
String s = sList.get(0);
類型擦除之后,當(dāng)調(diào)用sList.get(0)是如何確保返回的值不會(huì)和 String 不匹配呢?
用javap -c查看一下字節(jié)碼
public class com.example.demo.test.main {
// .....省略
public static void main(java.lang.String[]) throws java.lang.NoSuchFieldException;
Code:
0: new #2 // class java/util/ArrayList
3: dup
4: invokespecial #3 // Method java/util/ArrayList."<init>":()V
7: astore_1
8: aload_1
9: ldc #4 // String 111
11: invokevirtual #5 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
14: pop
15: aload_1
16: iconst_0
17: invokevirtual #6 // Method java/util/ArrayList.get:(I)Ljava/lang/Object;
20: checkcast #7 // class java/lang/String
23: astore_2
24: return
}
在#7處有一個(gè)checkcast指令,checkcast用于檢查類型強(qiáng)制轉(zhuǎn)換是否可以進(jìn)行,也就是泛型在獲取值的時(shí)候進(jìn)行了強(qiáng)制類型轉(zhuǎn)換。
- 再來看看下面這段代碼
首先定義一個(gè) Java 泛型類
public class GenericClass<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
再定義一個(gè)子類繼承它
public class GenericClassTest extends GenericClass<Integer> {
@Override
public void setValue(Integer value) {
super.setValue(value);
}
@Override
public Integer getValue(){
return super.getValue();
}
}
在GenericClassTest中將GenericClass的泛型定義為Integer類型,并重寫了 get 和 set 方法,因?yàn)榇嬖陬愋筒脸?,父?code>GenericClass的泛型被擦除了。
用javap -c 查看一下GenericClass編譯后的字節(jié)碼

可以看到類型擦除后泛型變?yōu)榱?code>Object。那么GenericClass也就變?yōu)榱?/p>
public class GenericClass {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
這樣,父類GenericClass中 set 和 get 方法操作的是 Object 對(duì)象,而子類GenericClassTest 操作的是 Integer 對(duì)象,為什么還可以重寫?按照正常的繼承關(guān)系中,這應(yīng)該是重載。
按照重載的方式試一下

可以看到設(shè)置 Object 對(duì)象出現(xiàn)了紅波浪線,不允許這樣設(shè)置,看來確實(shí)是重寫,而不是重載。為什么會(huì)時(shí)重寫,這不是跟 Java 多態(tài)沖突么?繼續(xù)往下研究。
現(xiàn)在用javap -c看一下子類GenericClassTest的字節(jié)碼文件

在GenericClassTest中 get 和/set 方法都有兩個(gè),一個(gè)是操作 Object 對(duì)象一個(gè)是操作 Integer 對(duì)象。
操作 Integer 對(duì)象的是GenericClassTest定義的,操作 Object 對(duì)象的是由編譯器生成的。
再用javap -v 查看一下字節(jié)碼更詳細(xì)的信息。

編譯器生成的兩個(gè)操作 Object 對(duì)象的方法中多了兩個(gè)ACC_BRIDGE、ACC_SYNTHETIC標(biāo)志。
這就是虛擬機(jī)解決類型擦除和多態(tài)沖突問題的方法:使用橋接方法。
橋接方法方法是由編譯器生成的,我們?cè)诖a中并不能直接使用,但是可以通過反射拿到橋接方法再使用。
泛型一旦編譯過后,類型就被擦除了,那到了運(yùn)行時(shí),怎么獲取泛型信息?這就要使用 JDK 提供的 Type 類型接口了。
Type 類型
在沒有泛型之前,所有的類型都通過 Class 類進(jìn)行抽象,Class 類的一個(gè)具體對(duì)象就代表了一個(gè)類型。
在 JDK1.5 增加了泛型之后,擴(kuò)充了數(shù)據(jù)類型,將泛型也包含了。
JDK 在原來的基礎(chǔ)上增加了一個(gè)Type接口,它是所有類型的父接口,它的子類有
-
Class類: 原始/基本類型,包括平時(shí)我們所有的類、枚舉、數(shù)組、注解,還有 int、float 等基本類型 -
ParameterizedType接口:參數(shù)化類型,比如 List<String> -
TypeVariable接口:類型變量,比如 List<T>中的 T 就是參數(shù)化變量 -
GenericArrayType接口: 數(shù)組類型,比如 List<String>[]、T[] -
WildcardType接口:泛型表達(dá)式類型,比如 List< ? extends Number>
ParameterizedType
參數(shù)化類型,即帶有參數(shù)的類型,也就是帶有<>的類型
public interface ParameterizedType extends Type {
Type[] getActualTypeArguments();
Type getRawType();
Type getOwnerType();
}
-
getActualTypeArguments(): 獲取類型內(nèi)部的參數(shù)化類型 比如 Map<K,V>里面的 K,V 類型。 -
getRawType(): 類的原始類型,比如 Map<K,V>中的 Map 類型。 -
getOwnerType(): 獲取所有者類型(只有內(nèi)部類才有所有者,比如 Map.Entry 他的所有者就是 Map),若不是內(nèi)部類,此處返回 null。
實(shí)例:
public class GenericClass<T> {
private List<String> list;
private List<T> tList;
public static void main(String[] args) {
Class<GenericClass> genericClassClass = GenericClass.class;
Field[] declaredFields = genericClassClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Type genericType = declaredField.getGenericType();
if (genericType instanceof ParameterizedType) {
System.out.println("==========" + genericType.getTypeName() + "======ParameterizedType類型=====");
ParameterizedType parameterizedType = (ParameterizedType) genericType;
System.out.println("getActualTypeArguments:");
Type[] actualTypeArguments = (parameterizedType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(" " + actualTypeArgument);
}
Type rawType = (parameterizedType).getRawType();
System.out.println("getRawType:");
System.out.println(" " + rawType);
}
}
}
}
輸出
==========java.util.List<java.lang.String>======ParameterizedType類型=====
getActualTypeArguments:
java.lang.String
getRawType:
interface java.util.List
==========java.util.List<T>======ParameterizedType類型=====
getActualTypeArguments:
T
getRawType:
interface java.util.List
TypeVariable
類型變量,即泛型中的變量,例如:T、K、V 等變量,可以表示任何類;
注意: 與 ParameterizedType 的區(qū)別,TypeVariable 代表著泛型中的變量,而 ParameterizedType 則代表整個(gè)泛型。比如 List<T>中,T 是 TypeVariable 類型,List<T>是 ParameterizedType 類型
public interface TypeVariable<D extends GenericDeclaration> extends Type, AnnotatedElement {
Type[] getBounds();
D getGenericDeclaration();
String getName();
// JDK8新增的
AnnotatedType[] getAnnotatedBounds();
}
-
getBounds():類型對(duì)應(yīng)的上限,默認(rèn)為 Object 可以有多個(gè)。比如 List< T extends Number & Serializable>中的 Number 和 Serializable -
getGenericDeclaration(): 獲取聲明該類型變量實(shí)體,比如 GenericClass< T>中的 GenericClass -
getName():獲取類型變量在源碼中定義的名稱;
實(shí)例:
public class GenericClass<T extends Number> {
private T t;
public static void main(String[] args) {
Class<GenericClass> genericClassClass = GenericClass.class;
Field[] declaredFields = genericClassClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Type genericType = declaredField.getGenericType();
if (genericType instanceof TypeVariable) {
System.out.println("==========" + genericType.getTypeName() + "======TypeVariable類型=====");
TypeVariable typeVariable = (TypeVariable) genericType;
Type[] bounds = typeVariable.getBounds();
System.out.println("getBounds:");
for (Type bound : bounds) {
System.out.println(" " + bound);
}
System.out.println("getGenericDeclaration:");
System.out.println(" " + typeVariable.getGenericDeclaration());
System.out.println("getName:");
System.out.println(" " + typeVariable.getName());
}
}
}
}
輸出:
==========T======TypeVariable類型=====
getBounds:
class java.lang.Number
getGenericDeclaration:
class com.example.demo.test.GenericClass
getName:
T
GenericArrayType
泛型數(shù)組類型,用來描述 ParameterizedType、TypeVariable 類型的數(shù)組;例如:List<T>[] 、T[]、List<String>[]等。
注意: GenericArrayType 是來描述與泛型相關(guān)的數(shù)組,與 String[]、int[]、float[]這種類型不同。
public interface GenericArrayType extends Type {
Type getGenericComponentType();
}
-
getGenericComponentType():返回泛型數(shù)組中元素的 Type 類型,比如 List<String>[] 中的 List<String>
實(shí)例:
public class GenericClass<T extends Number> {
private List<String>[] lists;
private T[] ts;
public static void main(String[] args) {
Class<GenericClass> genericClassClass = GenericClass.class;
Field[] declaredFields = genericClassClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Type genericType = declaredField.getGenericType();
if (genericType instanceof GenericArrayType) {
GenericArrayType genericArrayType = (GenericArrayType) genericType;
System.out.println("==========" + genericType.getTypeName() + "======GenericArrayType類型=====");
Type genericComponentType = genericArrayType.getGenericComponentType();
System.out.println("getGenericComponentType:");
System.out.println(" " + genericComponentType);
}
}
}
}
輸出:
==========java.util.List<java.lang.String>[]======GenericArrayType類型=====
getGenericComponentType:
java.util.List<java.lang.String>
==========T[]======GenericArrayType類型=====
getGenericComponentType:
T
WildcardType
泛型表達(dá)式(通配符表達(dá)式)。例如:? extend Number、? super Integer。
注意: WildcardType 雖然是 Type 的子接口,但不代表一種類型,,表示的僅僅是類似 ? extends T、? super K 這樣的通配符表達(dá)式。
public interface WildcardType extends Type {
Type[] getUpperBounds();
Type[] getLowerBounds();
}
-
getUpperBounds()獲得泛型表達(dá)式上界(上限) 獲取泛型變量的上邊界(extends) -
getLowerBounds()獲得泛型表達(dá)式下界(下限) 獲取泛型變量的下邊界(super)
實(shí)例:
public class GenericClass<T extends Number> {
private List<? extends Number> numbers;
private List<? super Integer> integers;
public static void main(String[] args) {
Class<GenericClass> genericClassClass = GenericClass.class;
Field[] declaredFields = genericClassClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
Type genericType = declaredField.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type[] actualTypeArguments = (parameterizedType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
if(actualTypeArgument instanceof WildcardType){
System.out.println("==========" + actualTypeArgument.getTypeName() + "======WildcardType類型=====");
WildcardType wildcardType = (WildcardType) actualTypeArgument;
System.out.println("getUpperBounds:");
Type[] upperBounds = wildcardType.getUpperBounds();
for (Type upperBound : upperBounds) {
System.out.println(" "+ upperBound);
}
System.out.println("getLowerBounds:");
Type[] lowerBounds = wildcardType.getLowerBounds();
for (Type lowerBound : lowerBounds) {
System.out.println(" "+ lowerBound);
}
}
}
}
}
}
}
輸出:
==========? extends java.lang.Number======WildcardType類型=====
getUpperBounds:
class java.lang.Number
getLowerBounds:
==========? super java.lang.Integer======WildcardType類型=====
getUpperBounds:
class java.lang.Object
getLowerBounds:
class java.lang.Integer