Java 泛型擦除(Type Erasure)

Java 泛型擦除(Type Erasure)

(含匿名内部类保留泛型信息的特殊情况)

一、核心定义

泛型擦除是 Java 为兼容 1.5 前非泛型代码的编译期行为
编译时移除泛型参数信息,将泛型类型替换为“原始类型”,运行时不存在 List<String>/List<Integer> 区别,仅保留 List.class
→ 本质:泛型是“编译期语法糖”,运行时无泛型类型实例。

二、3类核心擦除规则

泛型场景 擦除规则 示例 擦除后结果(编译期)
类/接口泛型 无边界→Object;有边界→边界类型 class MyList<T> class MyList(T→Object)
class NumList<T extends Number> class NumList(T→Number)
方法泛型(参数/返回值) 按类泛型规则替换,同时插入类型转换代码 public <T> T get(T obj) public Object get(Object obj)
泛型通配符 ?Object? extends AA? super AObject List<? extends Number> List<Number>

编译期:编译器处理泛型代码时,会按规则擦除泛型信息(比如 List<String> 擦成 List、? extends Number 擦成 Number 等 ),同时插入必要的类型转换代码,保证编译时的类型检查能通过。
运行期:JVM 加载的字节码中,泛型的具体参数(如 String、Integer )已被擦除,只剩原始类型(如 Object、Number 对应的逻辑 ),运行时无法区分 List<String> 和 List<Integer> 的泛型差异。

泛型声明(<>及内部内容)被彻底删除,只剩原始类型List;
代码中使用泛型参数的地方(如元素类型)被替换为边界类型Number(保证字节码的类型兼容性)。

三、泛型信息的保留机制

擦除仅移除“运行时动态类型”,但部分场景会保留泛型元数据(存于字节码“签名”属性,供反射读取):

1. 常规保留场景(可反射获取)(声明处保留场景)

  • 类声明:class MyList<T>(用 getTypeParameters() 获取)
  • 方法参数:void func(List<String> list)(用 getGenericParameterTypes() 获取)
  • 方法返回值:List<Integer> getList()(用 getGenericReturnType() 获取)
  • 字段声明:private Map<String, Integer> map(用 getGenericType() 获取)

2. 特殊情况:匿名内部类的泛型保留

匿名内部类在实例化时若指定泛型参数,编译器会将泛型信息保留在类的元数据中(这是唯一能在“非声明处”保留泛型的场景)。

示例代码

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;

public class AnonymousGenericExample {
    // 这是一个普通的方法,不是泛型方法声明
    public void processList() {
        // 这里创建了一个匿名内部类(注意末尾的{})
        // 这个泛型参数<String>是在非声明处指定的
        ArrayList anonymousList = new ArrayList<String>() {};
        
        // 通过反射获取匿名内部类的泛型信息
        try {
            // 获取当前类中所有字段(虽然这里是局部变量,但原理相同)
            Type type = anonymousList.getClass().getGenericSuperclass();
            
            if (type instanceof ParameterizedType) {
                ParameterizedType paramType = (ParameterizedType) type;
                Type[] actualTypeArgs = paramType.getActualTypeArguments();
                
                System.out.println("匿名内部类的泛型参数: " + actualTypeArgs[0]);
                // 输出: 匿名内部类的泛型参数: class java.lang.String
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) {
        new AnonymousGenericExample().processList();
    }
}

原理
匿名内部类会生成独立的 .class 文件(如 AnonymousGeneric$1.class),编译器会将泛型参数(如 String)写入该类的元数据,因此反射可获取。

3. 不保留的泛型(反射也拿不到)

  • 局部变量:method内 List<String> list = new ArrayList<>()(非匿名内部类)
  • 普通实例化:new ArrayList<String>()(非匿名内部类,仅编译期检查)

四、实用结论

  1. 运行时 list.getClass() 只能拿到原始类型(如 ArrayList.class),无法区分泛型参数;
  2. 匿名内部类是特殊例外,其泛型参数会被保留在元数据中,可通过反射获取;
  3. 反射能获取的泛型信息,本质是“类结构中声明的泛型契约”,而非运行时动态类型;
  4. 泛型擦除后,编译器会自动插入类型转换代码,保证运行时类型安全(如 String s = list.get(0))。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容