基礎篇:深入解析JAVA泛型和Type類型體系
目錄
1 JAVA的Type類型體系
- 先了解下java的Type類型體系,Type是所有類型(原生類型-Class、參數化類型-Parameterizedtype、數組類型-GenericArrayType、類型變數-TypeVariable、基本類型-Class)的共同介面;前兩篇反射和註解講到的Class<T>就是Type的一實現類
- Type下面又有四個子介面類ParameterizedType、TypeVariable、GenericArrayType、WildcardType
- List<E>表示泛型,E是TypeVariable類型,List<String>則是ParameterizedType(參數化類型),List<String>里的String稱為實際參數類型
- 具體化泛型中的類型時,可以使用 ? extends 或 ? super來表示繼承關係;如
List<\? extends Data\>
,而裡面的 ? 稱為通配符類型 - GenericArrayType 表示一種元素類型是ParameterizedType(參數化類型)或者TypeVariable(類型變數)的數組類型,如T[] 或者 List[]
- 註解是JDK1.5才出現了的,為了表示被註解的類型的,加入AnnotatedElement類型,字面意思就是被註解的元素。JDK1.8又有了AnnotatedType將Type和被註解元素的概念關聯起來。
- AnnotatedType也有四個子介面,和Type的四個子介面一一對應,如:ParameterizedType類型被註解則被編譯器解析成AnnotatedParameterizedType:
@AnTest("list")List<String>list
2 泛型的概念
- Java 泛型(generics)是JDK1.5中引入的一個新特性,其本質是參數化類型,解決不確定具體對象類型的問題;其所操作的數據類型被指定為一個參數(type parameter)這種參數類型可以用在類、介面和方法的創建中,分別稱為泛型類、泛型介面、泛型方法
泛型: 把類型明確的工作推遲到創建對象或調用方法的時候才去明確的特殊的類型
3 泛型類和泛型方法的示例
- 泛型類的定義
public class MainTest<T> {
private T param;
}
// 存在上界Object
class MainTest<T extends Object> { }
// 存在上界Object
class MainTest<T extends Object> { }
public static void main(String[] args){
MainTest<String> data = new MainTest<String>(){};
ParameterizedType genType1 = (ParameterizedType)data.getClass().getGenericSuperclass();
}
- 泛型方法的定義
public class MainTest{
public static void main(String[] args){
printData("siting");
}
static <T> T printData(T t){
System.out.println(t);
return t;
}
}
- 介面和抽象類都可以使用泛型
4 類型擦除
- 創建泛型的實例時,jvm是會把具體類型擦除的;編譯生成的位元組碼中不包含泛型中的類型參數,即都擦除成了ArrayList,也就是被擦除成”原生類型”,這就是泛型擦除
public class MainTest {
public static void main(String[] args){
List<String> strArr = new ArrayList<>();
List<Integer> intArr = new ArrayList<>();
Type strClazz = strArr.getClass();
Type intClazz = intArr.getClass();
}
}
- 查看編譯後的位元組碼文件是如何表示的: idea菜單 -> view -> show ByteCode
public class MainTest<T> {
T param;
public static void main(String[] args){
MainTest<String> test = new MainTest<>();
test.setParam("siting");
}
public T getParam() { return param; }
public void setParam(T param) { this.param = param; }
}
public class com/MainTest {
...省略
public static main([Ljava/lang/String;)V
L0
LINENUMBER 7 L0
NEW com/MainTest
DUP
INVOKESPECIAL com/MainTest.<init> ()V
ASTORE 1
L1
LINENUMBER 8 L1
ALOAD 1
LDC "siting" // 調用類型擦除後的setParam(Object)
INVOKEVIRTUAL com/MainTest.setParam (Ljava/lang/Object;)V
L2
...省略//getParam 的返回值是Object
public getParam()Ljava/lang/Object;
L0
LINENUMBER 10 L0
ALOAD 0
GETFIELD com/MainTest.param : Ljava/lang/Object;
ARETURN
...省略//setParam 的入參是Object
public setParam(Ljava/lang/Object;)V
L0
LINENUMBER 11 L0
ALOAD 0
ALOAD 1
PUTFIELD com/MainTest.param : Ljava/lang/Object;
RETURN
...
}
- 可以看出T(String)都被轉換為Object類型,最初的初始化的String不見了
5 參數化類型ParameterizedType
public interface ParameterizedType extends Type {
//獲取實際參數,List<String>里的String; 如果是List<T>則是TypeVariable類型
Type[] getActualTypeArguments();
// 獲取原始類型List<String> -> List<E>
Type getRawType();
Type getOwnerType();
}
- 需要注意的點,我們不能直接獲取指定具體參數的泛型的類型,如
Class clazz = List<String>.class
編譯時不通過的;還有就是直接通過泛型類new創建的對象,其Class並非ParameterizedType類型,而是泛型本身的class,示例如下
public class MainTest<T> {
public static void main(String[] args){
MainTest<String> str = new MainTest<String>();
Class variable = str.getClass();
Type genType1 = variable.getGenericSuperclass();
}
}
- 被具體參數化的泛型才能被編譯器識別為ParameterizedType類型,有三種方式獲取ParameterizedType類型
// 1 子類繼承泛型時,指定具體參數(可以是String等已知類型,也可以是子類的泛型參數)
// 2 獲取在類內部定義的泛型屬性
// 3 局部程式碼,可以通過泛型的匿名內部子類獲取ParameterizedType類型
public class MainTest<T> {
List<T> list;
public static void main(String[] args) throws NoSuchFieldException {
SubTest<String> str = new SubTest<>();
// 方式一
Class variable = str.getClass();
// 父類是(521)ParameterizedType類型
ParameterizedType genType = (ParameterizedType)variable.getGenericSuperclass();
// (521)ParameterizedType類型的原生類型是(479)class com.MainTest
Type clazz = genType.getRawType();
//MainTest.class 的原生類型是 (479)class com.MainTest
Class rawClazz = MainTest.class;
//方式二,泛型屬性
Field field = rawClazz.getDeclaredField("list");
//屬性list 類型是(546)ParameterizedType類型List<T>
ParameterizedType fieldType = (ParameterizedType)field.getGenericType();
// 方式三
MainTest<String> sub3 = new MainTest<String>(){};
// clazz3是匿名子類
Class clazz3 = sub3.getClass();
//父類是(555)ParameterizedType類型
ParameterizedType genType3 = (ParameterizedType) clazz3.getGenericSuperclass();
// (555)ParameterizedType類型的原生類型是(479)class com.MainTest
Type type3 = genType3.getRawType();
}
public static class SubTest<R> extends MainTest<R>{ }
}
6 泛型的繼承
- 子類可以指定父類的泛型參數,可以是已知類(Integer、String等),也可以用子類自己的泛型參數指定
- 泛型被繼承時,且指定父類泛型參數,則額外生成的ParameterizedType類型作為子類的父類;如果沒有指定父類泛型參數,則直接繼承原生類型
public class MainTest<T> {
T param;
static public class SubTest1 extends MainTest<String>{}
static public class SubTest2<R> extends MainTest<R>{}
//SubTest3繼承的時原生類型
static public class SubTest3 extends MainTest{}
}
7 泛型變數TypeVariable
- (先臨時定義一個名稱,Test<E>里的E為泛型參數);泛型變數TypeVariable:泛型的泛型參數就是TypeVariable;當父類使用子類的泛型參數指定自身的泛型參數時;或者泛型屬性定義在泛型類A<T>中,並使用泛型類A<T>的泛型參數T時,其泛型參數都會被編譯器定為泛型變數TypeVariable,而不是被擦除
public class MainTest<T> {
List<T> param;
public static void main(String[] args) throws Exception{
Class clazz = MainTest.class;
TypeVariable[] typeVariable = clazz.getTypeParameters();
// 1
Field field = clazz.getDeclaredField("param");
ParameterizedType arrayType = (ParameterizedType)field.getGenericType();
// interface List<E> 的泛型類型E被T,具體化,因此其被識別為 TypeVariable
TypeVariable variable1 = (TypeVariable)arrayType.getActualTypeArguments()[0];
// 2
ParameterizedType type = (ParameterizedType)SubTest.class.getGenericSuperclass();
TypeVariable variable2 = (TypeVariable)type.getActualTypeArguments()[0];
}
static class SubTest<R> extends MainTest<R>{}
}
8 通配符(WildcardType)
無邊界通配符:無界通配符 ? 可以適配任何引用類型:
- 當方法參數需要傳入一個泛型時,而且無法確定其類型時。直接使用無具體泛型變數的泛型,容易造成安全隱患;若在方法程式碼里進行類型轉換,極容易出現ClassCastException錯誤
- 那泛型變數用Object代替不就行了?但是泛型類+的類型具體參數轉變的ParameterizedType(參數化類型)是不存在繼承關係,是不同的類型;即Object是String的父類,但是List<Object>和List<String>的類型是不同的兩個ParameterizedType,不存在繼承關係。於是有了類型通配符 ?
public static void print(List list){}
----->>>
public static void print(List<?> list){}
- 無界通配符可以匹配任意類型;但是在使用?時,不能給泛型類的變數設置值,因為我們不知道具體類型是什麼;如果強行設置新值,後面的讀容易出現ClassCastException錯誤。因此編譯器限制了通配符 ?的泛型只能讀不能寫
上界限定通配符 < ? extends E>
- 想接收一個List集合,它只能操作數字類型的元素【Float、Integer、Double、Byte等數字類型都行】,怎麼做?可以使用
List<? extends Number的子類>
,表明List里的元素都是Number的子類
public static void print(List<? extends Number> list) {
Number n = new Double("1.0");
list.add(n);
Number tmp = list.get(0);
}
- 圖片里可以看出,存在上界通配符,因為具體類型不確定,也是只能讀不能寫的
下界限定通配符 < ? super E>
class Parent{ }
class Child extends Parent{ }
public class MainTest<T> {
T param;
public static void main(String[] args){
MainTest<? super Child> parent_m = new MainTest<>();
parent_m.setParam(new Child());
Object parent = parent_m.getParam();
}
public T getParam() { return param; }
public void setParam(T param) { this.param = param; }
}
- 如果定義了通配符是誰的父類,則是下界限定通配符;此類通配符可讀可寫,轉成任意父類都不會出現ClassCastException錯誤。
- 個人猜想:難道是因為通配符和上界限定通配符的泛型 向下轉型容易出現ClassCastException錯誤,而下界限定通配符向上轉型不會出現ClassCastException錯誤,因此java規範限制前者編譯出錯,而後面編譯通過?
9 泛型數組(GenericArrayType)
public interface GenericArrayType extends Type {
//獲得這個數組元素類型,即獲得:A<T>(A<T>[])或T(T[])
Type getGenericComponentType();
}
-
GenericArrayType,泛型數組,描述的是ParameterizedType類型以及TypeVariable類型數組,即形如:Test<T\>[][]、T[]等,是GenericArrayType的子介面
public class MainTest<T> {
T[] param;
public static void main(String[] args) throws Exception{
Class clazz = MainTest.class;
Field field = clazz.getDeclaredField("param");
GenericArrayType arrayType = (GenericArrayType)field.getGenericType();
TypeVariable variable = (TypeVariable) arrayType.getGenericComponentType();
}
}