为何单类型参数泛型经捕获转换后出现两个新类型变量?
定义的泛型类
class SimpleClass<T> { public T t1; public T t2; public SimpleClass(T t1, T t2) { this.t1 = t1; this.t2 = t2; } }
测试代码
SimpleClass<?> p = new SimpleClass<String>("a", "b"); p.t1 = p.t2;
编译错误信息
error: incompatible types: CAP#1 cannot be converted to CAP#2 p.t1 = p.t2; ^ where CAP#1,CAP#2 are fresh type-variables: CAP#1 extends Object from capture of ? CAP#2 extends Object from capture of ?
用户的理解与疑问
变量p的声明类型为SimpleClass<?>,原本认为捕获转换后会得到类似如下的类:
class SimpleClass_cap#1<CAP#1> { public CAP#1 t1; public CAP#1 t2; public SimpleClass(CAP#1 t1, CAP#1 t2) { this.t1 = t1; this.t2 = t2; } }
这一理解基于JLS(Java语言规范)中的描述:
Let G name a generic type declaration (§8.1.2, §9.1.2) with n type parameters
A_1,...,A_n with corresponding bounds U_1,...,U_n.There exists a capture conversion from a parameterized type G<T_1,...,T_n> (§4.5) to
a parameterized type G<S_1,...,S_n>, where, for 1 ≤ i ≤ n :• If T_i is a wildcard type argument (§4.5.1) of the form ?, then S_i is a fresh type
variable whose upper bound is U_i[A_1:=S_1,...,A_n:=S_n] and whose lower bound
is the null type (§4.1).
请问:将p声明为SimpleClass<?>后,为何会出现两个新类型变量CAP#1和CAP#2?
核心原因是:编译器对每个成员变量的类型进行捕获转换时,是独立处理的,而非将整个泛型类的通配符统一捕获为单个类型变量。
你对捕获转换的理解存在误区:捕获转换不是生成一个新的泛型类,而是针对变量的使用场景,对每个类型引用单独做捕获。当访问p.t1和p.t2时,编译器会分别对这两个成员的类型进行通配符捕获:
- 访问
p.t1时,编译器将?捕获为CAP#1,所以t1的类型是CAP#1; - 访问
p.t2时,编译器又将?捕获为另一个独立的CAP#2,所以t2的类型是CAP#2。
虽然运行时CAP#1和CAP#2实际都是String,但编译阶段编译器只能看到SimpleClass<?>的声明——它无法确定通配符具体代表的类型,更无法保证两次捕获的类型变量是同一个。从编译器的角度看,CAP#1和CAP#2是两个无关联的未知类型,自然不允许互相赋值。
再看JLS的描述,它针对的是**参数化类型G<T_1,...,T_n>**的捕获转换,得到G<S_1,...,S_n>,这里的S_1对应泛型类的单个类型参数。但实际代码中,访问泛型类成员时,编译器是对每个成员的类型(即T对应的通配符)分别捕获,而非提前将整个泛型实例的类型统一绑定到一个捕获变量上。
简单来说:编译器不会为p这个实例“缓存”捕获后的类型变量,每次访问成员时都会重新捕获通配符,导致生成两个独立的CAP类型。
内容的提问来源于stack exchange,提问作者yanpengl

