javac源码中t.tsym.type访问类型属性相比直接访问t的设计目的
javac中
t.tsym.type.xxx访问模式的设计逻辑 规范化处理的核心效果
这种写法是javac内部统一类型元数据访问的标准惯用模式,本质是做了一层访问入口的归一,解决三类通用问题:
- 异常类型兜底:编译期生成的
ErrorType、MissingType等错误占位类型,自身的supertype_field、interfaces_field等元数据字段大多是空值或者错误占位值,但只要关联的tsym已经完成解析,就能通过tsym.type拿到已经初始化完成的规范类型实例,避免空指针或者返回错误的占位结果。 - 临时实例适配:javac在类型推断、泛型擦除、注解处理等阶段会生成大量临时
Type副本,这些副本往往只填充了当前流程需要的部分字段,元数据并不完整。但所有这类临时实例的tsym都指向全局唯一的对应符号,tsym.type永远指向该符号绑定的基准规范类型实例——也就是类加载完成后全量元数据初始化完毕的正式类型对象,不会拿到临时副本的残缺字段。 - 构造阶段容错:
ClassType和ClassSymbol的循环引用绑定是分阶段完成的,存在ClassType实例已经创建、但自身supertype_field等属性还未赋值,而对应ClassSymbol已经完成type字段绑定、且规范类型的元数据已经初始化完毕的窗口期,这层跳转可以避免拿到构造过程中的null值。
无编译错误场景下的实质性差异
就算排除所有编译错误、所有ClassType与ClassSymbol都正常双向绑定,两种访问方式在以下场景会出现明确的结果差异:
- 访问泛型参数化类型时
如果你拿到的t是List<String>这类参数化类型实例,直接访问t.supertype_field拿到的是适配了实际类型参数的父类AbstractCollection<String>;而t.tsym.type指向的是List类声明对应的原生泛型类型List<T>,访问其supertype_field拿到的是泛型父类AbstractCollection<T>,二者是完全不同的类型实例。 - 访问临时生成的类型副本时
类型推断、类型变量替换、泛型擦除流程中会克隆生成大量临时Type实例,这些实例往往只更新和当前处理逻辑相关的字段,父类、接口等通用元数据字段可能延迟填充甚至直接复用旧值,直接访问t.xxx可能拿到null或者不符合预期的旧值;而t.tsym.type永远指向全局唯一的基准类型,拿到的元数据是完整准确的。
这也是OpenJDK JDK18中
Types.supertype(Type)方法优先走t.tsym.type跳转的核心原因:该方法接收的入参可能是任意阶段生成的临时类型实例,先跳转到基准规范类型再取父类,可以省略大量针对特殊类型实例的分支判断,保证逻辑统一。
- 访问注解处理阶段生成的代理类型时
注解处理器生成的代理类型、类型别名包装类,会复写tsym字段指向真实的目标类符号,但自身的元数据字段可能为了适配处理器逻辑做了特殊修改,这时候直接访问t.xxx拿到的是代理层修改后的值,访问t.tsym.type.xxx才能拿到真实类型的原始元数据。
只有当传入的t本身就是类符号绑定的基准规范类型实例时,t和t.tsym.type才会指向同一个对象,两种访问方式的结果才完全一致。
内容的提问来源于stack exchange,提问作者Laird Nelson
相关产品推荐
相关产品推荐

