Java 17 为何允许 instanceof 检查带泛型实参的类型?
结论
这段代码在 Java 17 中合法,类型擦除仍然生效。obj 的静态类型已经是 Gen2<Integer>,因此编译器允许这种比较,运行时不需要检查 Integer。
Java 16 之前,instanceof 右侧的类型必须是可具体化类型(reifiable type)。从 Java 16 开始,部分带具体类型实参的参数化类型也可以用于比较。Java 17 语言规范 §15.20.2 说明了这一变化。
为什么原示例能够编译?
问题中的代码是:
Gen2<Integer> obj = new Gen2<Integer>();
System.out.println(obj instanceof Gen2<Integer>);
编译期和运行期检查的内容不同:
- 编译期:
obj的静态类型是Gen2<Integer>,转换到相同类型不需要未经检查的转换(unchecked conversion)。 - 运行期:只检查对象是否属于
Gen2或其子类,不检查泛型实参Integer。
示例中,obj 引用 new Gen2<Integer>() 创建的对象,所以结果是 true。如果同类型变量的值为 null,instanceof 的结果则是 false。
书中“无法编译”的说明符合 Java 16 之前的限制。仅凭给出的摘录,无法确定书中变量 iOb2 的完整声明。这句话也不能作为 Java 17 中所有参数化类型比较的通用规则。
换成 Object,为什么又会报错?
把同一个对象交给静态类型为 Object 的变量,情况就不同了:
Object value = new Gen2<Integer>();
// 编译错误:无法安全地检查具体泛型实参
// System.out.println(value instanceof Gen2<Integer>);
// 合法:只检查是否属于 Gen2,不要求确定类型实参
System.out.println(value instanceof Gen2<?>);
Object 没有提供类型实参信息。从 Object 转换到 Gen2<Integer> 需要未经检查的转换,而运行时无法补上这部分检查,因此这种 instanceof 写法仍被禁止。
Gen2<?> 表示类型实参未知。检查它只需要确认对象是否属于 Gen2 或其子类,不必区分 Gen2<Integer> 和 Gen2<String>。
在泛型继承体系中应该怎样写?
如果父类引用已经保留了 Integer 这一编译期信息,Java 17 也允许进一步检查子类:
Gen<Integer> value = new Gen2<Integer>();
System.out.println(value instanceof Gen2<Integer>); // true
根据题目中的声明,Gen2<T> 继承 Gen<T>。因此,从 Gen<Integer> 检查是否为 Gen2<Integer>,不需要运行时验证新的泛型实参。这与规范中从 List<Integer> 检查 ArrayList<Integer> 的例子相同。Java 17 语言规范示例
实际排查时,先看左侧表达式的静态类型,再确认编译时使用的语言级别。即使用 JDK 17,通过 --release 15 编译也会受到旧规则限制。如果只需判断对象属于哪个泛型类,可以使用 instanceof Gen2<?>;这个检查本身不能证明类型实参是 Integer。
备注:内容仅供参考。
