Java 泛型採用類型擦除實現,編譯期類型信息在運行時大幅喪失。但 JDK 在 Class 文件格式中保留了簽名屬性,使得聲明處的泛型信息(類/介面/方法/欄位簽名)可通過反射 API 恢復。本文系統梳理 java.lang.reflect.Type 體系、核心 API、多級泛型解析實戰與常見陷阱。
Type (介面,所有類型的共同父介面)
├── Class // 普通類、介面、枚舉、註解、基本類型、數組
├── ParameterizedType // 參數化類型:List, Map
├── TypeVariable // 類型變量:T, K, V,
├── GenericArrayType // 泛型數組:T[], List[]
└── WildcardType // 通配符類型:? extends Number, ? super String
| 目標 | 獲取泛型信息方法 | 返回類型 | 說明 |
|---|---|---|---|
| 類 | getGenericSuperclass() | Type | 父類泛型簽名(如 Base<List<String>>) |
| 類 | getGenericInterfaces() | Type[] | 實作介面泛型簽名陣列 |
| 類 | getTypeParameters() | TypeVariable[] | 類自身聲明的類型參數(如 <T>) |
| 方法 | getGenericReturnType() | Type | 方法返回值泛型簽名 |
| 方法 | getGenericParameterTypes() | Type[] | 方法參數泛型簽名陣列 |
| 方法 | getGenericExceptionTypes() | Type[] | 方法拋出異常泛型簽名 |
| 欄位 | getGenericType() | Type | 欄位聲明泛型簽名 |
import java.lang.reflect.*;
import java.util.*;
public class GenericTypeResolver {
/**
* 將任意 Type 解析為原始 Class(支援多級泛型、數組、類型變量)
* @param type 目標 Type
* @param typeArgIndex ParameterizedType 時指定第幾個類型參數(0-based)
* @return 擦除後的原始 Class,無法解析返回 Object.class
*/
public static Class> resolveRawClass(Type type, int typeArgIndex) {
if (type instanceof ParameterizedType) {
return resolveRawClass(((ParameterizedType) type).getActualTypeArguments()[typeArgIndex], 0);
} else if (type instanceof TypeVariable) {
// 類型變量:取第一個邊界(上界),預設 Object
Type[] bounds = ((TypeVariable>) type).getBounds();
return bounds.length > 0 ? resolveRawClass(bounds[0], 0) : Object.class;
} else if (type instanceof GenericArrayType) {
// 泛型數組:遞歸解析組件類型
return Array.newInstance(
resolveRawClass(((GenericArrayType) type).getGenericComponentType(), 0), 0).getClass();
} else if (type instanceof WildcardType) {
// 通配符:取上界第一個
Type[] upper = ((WildcardType) type).getUpperBounds();
return upper.length > 0 ? resolveRawClass(upper[0], 0) : Object.class;
} else {
// Class、PrimitiveType 直接強轉
return (Class>) type;
}
}
/**
* 獲取 ParameterizedType 指定索引的實際類型參數原始 Class
*/
public static Class> getActualTypeArgument(ParameterizedType pType, int index) {
Type arg = pType.getActualTypeArguments()[index];
return resolveRawClass(arg, 0);
}
}
interface GenericInterface {
T method1(T obj);
}
interface CommonInterface {
Integer method2(Integer obj);
}
class BaseGeneric implements GenericInterface {
protected R result;
@Override public R method1(R obj) { return obj; }
}
// 關鍵:具體類指定父類/介面泛型參數為 List
class ConcreteGeneric extends BaseGeneric>
implements GenericInterface>, CommonInterface {
@Override public List method1(List obj) {
result = obj; return result;
}
public Integer method2(Integer obj) { return obj; }
// 方法自身聲明泛型(聲明處保留)
public T method3(T obj) throws E { return obj; }
}
public static void parseClassGenerics() {
ConcreteGeneric cg = new ConcreteGeneric();
// 1. 實作介面泛型:GenericInterface>
Type[] ifaces = cg.getClass().getGenericInterfaces();
for (Type t : ifaces) {
System.out.println("介面: " + t);
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
System.out.println(" 原始類型: " + pt.getRawType());
System.out.println(" 參數[0]: " + getActualTypeArgument(pt, 0)); // 輸出: class java.util.List
}
}
// 2. 父類泛型:BaseGeneric>
Type superType = cg.getClass().getGenericSuperclass();
if (superType instanceof ParameterizedType) {
System.out.println("父類參數[0]: " + getActualTypeArgument((ParameterizedType)superType, 0)); // List
}
// 3. 類自身類型參數:ConcreteGeneric 無自身參數,輸出空
for (TypeVariable> tv : cg.getClass().getTypeParameters()) {
System.out.println("類參數: " + tv.getName() + " bounds=" + Arrays.toString(tv.getBounds()));
}
}
public static void parseMethodGenerics() throws NoSuchMethodException {
Method m3 = ConcreteGeneric.class.getDeclaredMethod("method3", Object.class);
// 參數類型:T (類型變量,上界 Object)
for (Type pt : m3.getGenericParameterTypes()) {
System.out.println("參數: " + pt + " -> " + resolveRawClass(pt, 0)); // Object
}
// 異常類型:E extends Throwable
for (Type et : m3.getGenericExceptionTypes()) {
System.out.println("異常: " + et + " -> " + resolveRawClass(et, 0)); // Throwable
}
// 返回類型:T
System.out.println("返回: " + m3.getGenericReturnType() + " -> " + resolveRawClass(m3.getGenericReturnType(), 0)); // Object
}
public static void parseFieldGenerics() throws NoSuchFieldException {
// 欄位在父類 BaseGeneric 中聲明:protected R result;
Field field = BaseGeneric.class.getDeclaredField("result");
// getGenericType() 返回類型變量 R
Type genericType = field.getGenericType(); // R
System.out.println("泛型類型: " + genericType + " -> " + resolveRawClass(genericType, 0)); // Object (擦除上界)
// getType() 直接返回擦除後類型
System.out.println("擦除類型: " + field.getType()); // Object
}
介面: GenericInterface>
原始類型: interface GenericInterface
參數[0]: class java.util.List
介面: CommonInterface
父類參數[0]: class java.util.List
參數: T -> class java.lang.Object
異常: E -> class java.lang.Throwable
返回: T -> class java.lang.Object
泛型類型: R -> class java.lang.Object
擦除類型: class java.lang.Object
ConcreteGeneric extends BaseGeneric<List<String>> 中具體化的 List<String> 完整保留在 Class 文件簽名中,可通過 getGenericSuperclass()/getGenericInterfaces() 恢復List list = new ArrayList<>(); 、欄位聲明在父類中的類型變量 R、方法自身聲明的類型參數 <T, E>,運行時均擦除為上界(Object/Throwable)new GenericInterface() {...} 會生成具體化簽名,可恢復 Integer| 陷阱 | 原因 | 應對 |
|---|---|---|
試圖獲取局部變量泛型 List 的 String | 局部變量屬「使用處」,Class 文件無簽名屬性 | 改用匿名內部類或將變量提升為欄位 |
父類聲明 R,子類指定 List,試圖在父類方法中獲取 String | 父類編譯時不知子類具體化類型,R 擦除為 Object | 子類覆寫方法並聲明具體類型,或通過 getGenericSuperclass() 在子類解析 |
構建 T[] 數組或 new T() | 類型變量擦除後無法確定運行時類型 | 傳入 Class 參數,用 Array.newInstance(clazz, n) / clazz.getDeclaredConstructor().newInstance() |
「位於聲明一側的,源碼裡寫了什麼到運行時就能看到什麼;位於使用一側的,源碼裡寫什麼到運行時都沒了。」
Signature 屬性Java 泛型 Type 體系雖複雜,但掌握四大 Type 子類型、聲明處保留 vs 使用處擦除、核心反射 API 對應表三大支柱,配合通用解析工具類,即可應對絕大多數運行時類型恢復需求。記住:只能恢復「寫在聲明上的」泛型信息,其餘皆為編譯期幻覺。