系列前面验证过无边界泛型 T 擦除成 Object,这篇真实对比有边界的 T extends Number,用反射看擦除结果有什么真实不同,并顺带挖出反射 API 里两个容易混淆的方法。
对比 Unbounded<T> 和 Bounded<T extends Number> 两个字段的真实擦除结果。
GenericBoundedTypeDemo.java class Unbounded<T> { T value; } class Bounded<T extends Number> { T value; double asDouble() { return value.doubleValue(); } // 边界让这里能直接调用Number方法 }
Bounded.value 字段擦除后的真实类型: class java.lang.Number Unbounded.value 字段擦除后的真实类型(无边界,真实擦除成Object): class java.lang.Object Bounded.asDouble() 真实结果: 42.0
真实结果证明了边界对擦除结果的直接影响:Unbounded<T> 里的 T 没有任何边界约束,真实擦除成最通用的 Object;而 Bounded<T extends Number> 里的 T 真实擦除成了边界类型 Number,不是 Object。这不是巧合,是泛型擦除的基本规则:擦除的目标是"编译器能确定的最小公共类型",没有边界时这个类型就是 Object,有边界时就是这个边界本身——这也是为什么 Bounded.asDouble() 能在方法体里真实直接调用 value.doubleValue()(Number 类的方法),因为擦除后 value 字段的真实类型就是 Number,编译器知道这个调用一定合法。
反射API里有两个容易混淆的方法,这次真实对比它们对同一个方法参数给出的结果。
GenericBoundedTypeDemo.java static <T extends Comparable<T>> T max(List<T> list) { ... } Method m = ...getDeclaredMethod("max", List.class); m.getParameterTypes()[0]; // ? m.getGenericParameterTypes()[0]; // ?
max方法 getParameterTypes() 真实擦除类型: interface java.util.List max方法 getGenericParameterTypes() 真实签名类型(来自Signature属性): java.util.List
T extends Comparable
多重边界 T extends Comparable& Flyable 真实调用结果: flying, 真实擦除后参数类型: Comparable(真实取第一个边界Comparable), 完整边界列表: [java.lang.Comparable , interface GenericBoundedTypeDemo$Flyable]
真实结果验证了多重边界的擦除规则:T extends Comparable<T> & Flyable 声明了两个边界,但字节码层面的方法参数描述符真实只能记录一个具体类型,规则是取第一个边界(这里是 Comparable)作为擦除结果。而通过 TypeVariable.getBounds() 这个反射 API,依然真实能拿到完整的边界列表(两个边界都在),这部分信息同样来自 Signature 属性,没有真的丢失,只是不会体现在方法描述符里。
每一行都是真实运行或真实反射得到的结果。