Java泛型:为何c2=c1赋值编译失败而c1=c0可正常编译?
泛型赋值编译行为差异原因
问题复现
两段代码表现不一致:
- 双参数泛型场景:
c1 = c0赋值可正常通过编译,c2 = c1赋值触发编译错误 - 单参数泛型场景:所有赋值操作均能正常编译通过
编译报错的双参数代码
class TwoParam <X extends Runnable, Y extends X> {} public class Main{ <U extends Thread> void foo(){ List<TwoParam<U, ? super Thread>> c0 = null; List<TwoParam<U, U>> c1 = c0; List<TwoParam<U, ? super Thread>> c2 = c1; } }
正常编译的单参数代码
class OneParam <X extends Runnable> {} public class Main{ public void bar(){ List<OneParam<? super Runnable>> a0 = null; List<OneParam<Runnable>> a1 = a0; List<OneParam<? super Runnable>> a2 = a1; } }
核心规则前置
理解差异需要先明确Java泛型的三个基础校验逻辑:
- 泛型默认不支持协变,只有右侧的参数化类型是左侧参数化类型的合法子类型时,赋值才能通过
- 通配符
? super T代表一个未知类型,取值范围是T本身以及T的所有父类。把带通配符的泛型赋值给确定类型的泛型时,编译器会做通配符捕获:只要存在至少一个合法的类型落在通配符范围内、且满足泛型类的所有边界约束,就判定赋值合法 - 如果泛型类的多个类型参数之间存在边界约束(比如
Y extends X),校验时会联动检查所有参数的约束关系,只要通配符范围内存在不满足约束的可能,就判定类型不匹配
单参数场景全量通过的原因
OneParam只有一个类型参数X,约束仅为X extends Runnable,没有跨参数的联动约束:
a1 = a0赋值时,a0的类型是List<OneParam<? super Runnable>>,通配符捕获可以直接把未知类型确定为Runnable——Runnable完全满足X extends Runnable的约束,属于合法匹配,编译通过a2 = a1赋值时,OneParam<Runnable>本身就是OneParam<? super Runnable>的子类型:确定类型Runnable正好落在? super Runnable的取值范围内,没有额外约束需要校验,直接判定匹配,编译通过。
双参数场景c2赋值失败的原因
TwoParam的两个类型参数存在强绑定约束:第二个参数Y必须是第一个参数X的子类(Y extends X),方法内的类型参数U本身有上界限制U extends Thread,也就是U只能是Thread或者Thread的子类:
c1 = c0赋值时,c0的类型是List<TwoParam<U, ? super Thread>>,编译器做通配符捕获时,只需要找到一个符合约束的类型即可:当U取到上界Thread时,第二个参数的未知类型可以直接匹配为Thread,此时Y=Thread、X=Thread,满足Y extends X的约束,因此判定赋值合法,编译通过。c2 = c1赋值时,c1的类型是List<TwoParam<U, U>>,要赋值给List<TwoParam<U, ? super Thread>>,这时候编译器需要校验通配符所有可能的取值都满足泛型约束:? super Thread的取值范围包含Thread、Runnable、Object等Thread的父类型,但泛型类要求第二个参数Y必须是X(也就是U)的子类。U本身最多是Thread类型,Runnable、Object这些类型都不可能是U的子类,直接违反Y extends X的约束。同时U本身可能是Thread的子类(比如自定义的MyThread extends Thread),这种情况下U本身也不在? super Thread的取值范围内,同样不匹配。
只要通配符范围内存在不满足约束的可能,编译器就会判定类型不匹配,因此这行赋值直接编译失败。
本质差异:单参数泛型没有跨参数的绑定约束,只要类型落在通配符范围内就判定合法;双参数泛型的第二个参数边界依赖第一个参数的实际类型,
? super Thread的取值范围无法满足“必须是U的子类”的强约束,因此触发编译错误。
内容的提问来源于stack exchange,提问作者anupamD
相关产品推荐
相关产品推荐

