为何directSupertypes()未按JLS 4.10要求返回原始类型?
Types#directSupertypes(TypeMirror)不返回参数化类型对应的原始超类型? Types#directSupertypes(TypeMirror)的文档说明该方法返回类型的“直接超类型”,并引用了Java语言规范(JLS)4.10章节。根据JLS 4.10的定义,参数化类型的直接超类型集合是该方法返回集合的超集——比如对于ArrayList<String>这类参数化类型,JLS明确其直接超类型包含原始类型ArrayList:
给定带有类型参数F₁,…,Fₙ(n > 0)的泛型类或接口C,参数化类型C<T₁,...,Tₙ>(其中每个Tᵢ(1 ≤ i ≤ n)都是一个类型)的直接超类型包括以下所有类型:
- 若C是类,则对C的直接超类类型应用替换[F₁:=T₁,…,Fₙ:=Tₙ]后得到的类型。
- 对C的直接超接口类型应用替换[F₁:=T₁,…,Fₙ:=Tₙ]后得到的类型。
- C<S₁,...,Sₙ>,其中Sᵢ包含Tᵢ(1 ≤ i ≤ n)。
- 若C是无直接超接口的接口,则类型
Object。- 原始类型C。
但实际调用directSupertypes传入ArrayList<String>时,返回集合中并没有包含原始类型ArrayList,核心原因在于Java Compiler API的设计目标与JLS语义的范围差异:
1. API聚焦静态类型检查的显式关系
JLS定义的是Java语言的完整语义,包含了为兼容JDK 5之前无泛型代码而保留的隐式类型兼容规则(比如参数化类型与原始类型的转换)。但Types工具类属于Java Compiler API的一部分,主要服务于注解处理器、编译器插件等工具场景,它只暴露编译器在静态类型检查过程中实际依赖的显式继承关系,而非JLS中为兼容性保留的隐式关系。
在现代静态类型检查逻辑中,ArrayList<String>的直接超类型被视为泛型化的父类AbstractList<String>和接口List<String>,原始类型ArrayList与参数化类型的兼容属于类型擦除后的降级规则,不会作为显式的直接超类型被处理。
2. API文档的隐含约束
虽然directSupertypes的文档引用了JLS 4.10,但实际上它的返回集合是JLS定义超集的子集。以javac(Oracle官方编译器实现)为例,该方法仅返回泛型化的直接父类和接口,不会包含原始类型——这是API实现遵循编译器内部类型模型的结果,而非文档描述的字面全覆盖。
3. 代码验证
你的示例代码已经直观验证了这一点:
Elements elements = ... Types types = ... TypeElement arrayList = elements.getTypeElement("java.util.ArrayList"); TypeElement string = elements.getTypeElement("java.lang.String"); DeclaredType arrayListTU = types.getDeclaredType(arrayList, string.asType()); List<? extends TypeMirror> directSupertypes = types.directSupertypes(arrayListTU); // 对应泛型化父类AbstractList<String> DeclaredType abstractListST = (DeclaredType)directSupertypes.get(0); assertEquals(1, abstractListST.getTypeArguments().size()); // List<String>是接口,排在返回列表末尾,说明集合中无原始类型 DeclaredType listST = (DeclaredType)directSupertypes.get(1); assertTrue(listST.asElement().getKind().isInterface());
替代方案
如果需要获取参数化类型对应的原始类型,可以直接调用types.erasure(arrayListTU)方法,该方法会返回类型擦除后的原始类型ArrayList。
内容的提问来源于stack exchange,提问作者Laird Nelson

