目录
[一、为什么 List.class 在语法上就不存在?](#一、为什么 List.class 在语法上就不存在?)
[执行层面:在 java 源文件 -> .class 文件的时候属性被擦除](#执行层面:在 java 源文件 -> .class 文件的时候属性被擦除)
[三、在java源代码编译成 .class 的时候](#三、在java源代码编译成 .class 的时候)
[五、getClass() 为什么拿不到泛型信息?](#五、getClass() 为什么拿不到泛型信息?)
[六、TypeToken 的核心:Super Type Token](#六、TypeToken 的核心:Super Type Token)
[七、Gson 的 TypeToken 实现中,关键逻辑就在无参构造器里:](#七、Gson 的 TypeToken 实现中,关键逻辑就在无参构造器里:)

一、为什么 List<Demo>.class 在语法上就不存在?
在 Java里,.class 是一个类字面量,它唯一合法的操作数是原始类型或数组类型的编译期名称。List<Demo> 是一个参数化类型,它在 Java 语法中不是一个可以独立求值的 "类型名字",所以 List<Demo>.class 在编译器看来就是一个非法的表达式,直接报错。

更根本的原因在于:JVM 在运行时根本不跟踪泛型类实例的类型参数。 new ArrayList<String>() 在运行时就是一个 ArrayList 实例,JVM 不会在对象头或 Class 元数据里记录"这个实例的E是 String"。既然运行时不存在一个代表 List<Demo> 的实体,自然也就不可能有一个对应的 .class 对象。
二、泛型擦除到底擦了什么,又保留了什么?
执行层面:在 java 源文件 -> .class 文件的时候属性被擦除
擦除的规则:类型变量擦除到最左边界(没有显式边界,就擦到Object),参数化类型擦除到原始类型。这意味着方法内部的字节码指令 、字段描述符 、方法描述符中,不再出现 <Demo> 这样的类型参数。
- 无边界泛型
<T>擦除 →Object
java
public <T> void test(T t) {}
// 擦除后
public void test(Object t) {}
- 有上边界
<T extends Number>擦除 →Number
java
public <T extends Number> void test(T t){}
//擦除后
public void test(Number t){}
- 多个边界
<T extends Serializable & Comparable<T>>最左边界是 Serializable,擦除到 Serializable
java
<T extends Serializable & Comparable<T>>
//擦除:Serializable
三、在java源代码编译成 .class 的时候
java
List<Demo> list = new ArrayList<>();
Demo d = list.get(0);
这个代码逻辑会变成
java
List list = new ArrayList();
Demo d = (Demo) list.get(0);
强转是编译器在擦除后为了保证类型正确性而插入的。
四、元数据层面:Signature属性保留了泛型签名
但 Class 文件里面还有一个 Signature 属性。对于任何自身是泛型的、或者签名中包含了类型变量或参数化类型实例的类、字段、方法,javac 都会发出 Signature 属性,把未擦除的泛型签名记录在元数据中。
所以更精准的表述时:擦除发生在 java源文件 编译成 .class 可执行文件的时候,而泛型签名被"归档"到了 Signature 属性里。 运行时直接问 JVM "这个 List 实例的泛型参数是什么",JVM 回答不上来,但是如果去看声明(字段声明、方法声明、类的父类声明),签名还在。
五、getClass() 为什么拿不到泛型信息?
list.getClass() 返回的是 class java.util.ArrayList
java
List<Demo> list = new ArrayList<>();
System.out.println(list.getClass());
打印的是

List.class 永远代表 List 本身,所有带有不同泛型参数的 List,运行时共用同一个 Class 对象
list1.getClass() 返回的是 ArrayList.class ,list2.getClass() 返回的也是 ArrayList.class
java
List<Demo> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // true
// 两者getClass拿到的都是 ArrayList.class,不是带泛型的
getClass() 是 Object 的方法,它反映的是实例的运行时类。JVM 不跟踪泛型类实例的类型参数,所以 List<Demo> 和 List<String> 的实例,getClass() 都返回同一个 ArrayList.class ,比较结果为true。
注意:这里用的是 ArrayList 而不是 List,因为运行时对象是 ArrayList 的实例。这恰好说明 getClass() 反映的是实现类,而不是声明类型,它和泛型参数没有任何关系。
六、TypeToken 的核心:Super Type Token
既然实例 不携带泛型参数,那有没有什么东西是JVM会跟踪泛型参数的?
有:泛型类的子类声明。
JVM虽然不跟踪 new ArrayList<String>() 的泛型参数,但如果有一个类显式继承了一个参数化类型,比如 class StringList extends ArrayList<String>,JVM会在 StringList 的Class元数据中记录"它的直接超类是 ArrayList<String> "。这个记录位于 Signature 属性中,可以通过Class.getGenericSuperclass() 读到。
Neal Gafter 的 Super Type Token 模式正是利用这一点:匿名内部类会生成一个真实的Class文件,它的直接超类是 TypeToekn<List<Demo>>,这个参数化超类的信息被写进了匿名类的 Signature 属性中。然后在 TypeToken 的构造器里调用 getClass().getGenericSuperclass(),就拿到 ParameterizedType,从而取出 List<Demo> 这个完整的 Type。
java
public class TypeToken<T> {
private final Class<? super T> rawType;
private final Type type;
private final int hashCode;
@SuppressWarnings("unchecked")
protected TypeToken() {
this.type = getTypeTokenTypeArgument();
this.rawType = (Class<? super T>) $Gson$Types.getRawType(type);
this.hashCode = type.hashCode();
}
private Type getTypeTokenTypeArgument() {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
ParameterizedType parameterized = (ParameterizedType) superclass;
if (parameterized.getRawType() == TypeToken.class) {
return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]);
}
}
// Check for raw TypeToken as superclass
else if (superclass == TypeToken.class) {
throw new IllegalStateException("TypeToken must be created with a type argument: new TypeToken<...>() {}; "
+ "When using code shrinkers (ProGuard, R8, ...) make sure that generic signatures are preserved.");
}
// User created subclass of subclass of TypeToken
throw new IllegalStateException("Must only create direct subclasses of TypeToken");
}
}
七、Gson 的 TypeToken 实现中,关键逻辑就在无参构造器里:
java
protected TypeToken() {
this.type = getTypeTokenTypeArgument();
...
}
private Type getTypeTokenTypeArgument() {
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof ParameterizedType) {
return ((ParameterizedType) superclass.getActualTypeArguments()[0];
}
...
}
new TypeToken<List<Demo>>(){} 这行代码编译时生成一个匿名子类 ,这个子类的父类泛型 TypeToken<List<Demo>> 会被存入子类 class 文件的 Signature 属性。匿名子类的名字为:Main$1,它的 .class 文件里面的属性字段的第一个就是 Signature 属性。这时候 superclass.getActualTypeArguments()0 字段就指向了 Signature。

运行时,在 TypeToken 无参构造器里:
getClass() → 获取匿名子类的 Class 对象,调用 getGenericSuperclass() ,读取子类的 Signature,拿到父类上写的泛型参数 List<Demo>.

它之所以强制用户写 new TypeToken<List<Demo>>(){} ,是为了确保 getClass() 返回的是一个直接继承了 TypeToken<List<Demo>> 的匿名子类,而不是 TypeToken 本身。