显式指定通配符上界是否有差异?基于JLS的泛型分析
这是个特别值得深究的问题——很多Java开发者都会凭直觉觉得这两组类型等价,但要从JLS(Java语言规范)角度严谨证明,得抠清楚几个核心规则,咱们一步步拆解:
一、非嵌套通配符:Generic<?> 与 Generic<? extends BaseType> 完全等价
先回到泛型类的定义:Generic<A extends BaseType>,这里类型参数A的上界被明确限定为BaseType。我们结合JLS的两个核心规则来分析:
1. 捕获转换规则(JLS §5.1.10)
捕获转换会把通配符替换为一个新鲜的类型变量,这个变量的边界由通配符和原泛型类型参数的约束共同决定:
- 对于
Generic<?>:捕获通配符?时,新鲜类型变量的上界继承自原类型参数A的上界——也就是BaseType。 - 对于
Generic<? extends BaseType>:捕获通配符? extends BaseType时,新鲜类型变量的上界同样是BaseType。
这意味着两种类型经过捕获转换后,得到的参数化类型本质上是一致的:都是带有上界为BaseType的新鲜类型变量的Generic实例。
2. 子类型与赋值规则(JLS §4.10.2、§5.2)
子类型关系的核心是赋值兼容性:如果类型S的所有实例都能赋值给类型T的变量,那么S是T的子类型。
- 任何
Generic<?>的实例,捕获后都是Generic<X>(X extends BaseType),显然可以赋值给Generic<? extends BaseType>的变量。 - 任何
Generic<? extends BaseType>的实例,捕获后都是Generic<Y>(Y extends BaseType),同样可以赋值给Generic<?>的变量。
因此,Generic<?>和Generic<? extends BaseType>互为子类型,本质上是等价的类型。
二、嵌套通配符:List<Generic<?>> 与 List<Generic<? extends BaseType>> 同样等价
很多人在这里会困惑,因为泛型默认是不变的(比如List<String>不是List<Object>的子类型),但这里的情况特殊:
核心前提:内层通配符类型等价
既然已经证明Generic<?>和Generic<? extends BaseType>是等价类型,那么对于外层的List来说,List<等价类型>自然也是等价类型。
不过我们可以用JLS的规则再严谨推导一遍:
根据JLS §4.10.2的参数化类型子类型规则,List<S>和List<T>互为子类型的前提是S和T互为子类型(因为List的类型参数没有额外的协变/逆变注解,但其子类型关系完全依赖于内部类型的子类型关系)。
既然Generic<?>和Generic<? extends BaseType>互为子类型,那么:
List<Generic<?>>是List<Generic<? extends BaseType>>的子类型List<Generic<? extends BaseType>>也是List<Generic<?>>的子类型
换句话说,二者互为子类型,本质上是同一个类型的两种写法。
总结一下:因为Generic的类型参数本身带有BaseType的上界约束,所以无边界通配符?在这里自动继承了该上界,和显式写? extends BaseType完全等价——不管是直接使用还是嵌套在其他泛型类型中。
内容的提问来源于stack exchange,提问作者phant0m

