Java泛型带界通配符非方法参数使用时extends与super表现相反问题咨询
你观察到的「边界规则和预期相反」的感受,本质是对带界通配符的核心语义存在误解——通配符的约束逻辑并没有颠倒,只是约束方向和代码的读写操作直接绑定。
方法参数中List<? extends RuntimeException>的遍历逻辑解释
List<? extends RuntimeException>的准确语义是:持有某一种固定的、继承自RuntimeException的未知类型元素的列表,注意这里的未知类型是固定的,不是“可以容纳所有RuntimeException子类元素的通用列表”。
你在foo方法中执行的是读操作:从列表中取出元素时,编译器可以保证不管这个未知类型具体是哪种子类,它一定是RuntimeException的子类型,因此取出的元素可以安全赋值给RuntimeException类型的循环变量,遍历逻辑自然可以正常运行。这里的extends代表的是元素类型的上界,仅对读操作的返回类型做约束。
add操作的编译报错原因
回到刚才的语义:List<? extends RuntimeException>指代的是某一个固定的未知子类型的列表,它的实际类型可能是List<IllegalArgumentException>,也可能是List<NullPointerException>,在声明变量时完全无法确定具体是哪一种。
如果你尝试往这个列表里添加IllegalArgumentException实例,万一这个列表实际是List<NullPointerException>,就会出现完全的类型不匹配问题。为了避免这种运行时风险,编译器禁止所有非null元素写入? extends声明的列表,不管你要写入的是RuntimeException本身还是它的子类,都会触发编译错误。你看到的报错里的capture#1,就是编译器为这个“未知的固定类型”生成的临时指代名称。
List<? super RuntimeException>的写入行为解释
List<? super RuntimeException>的准确语义是:持有某一种固定的、RuntimeException的父类型元素的列表,同样是未知的固定父类型,不是“可以容纳所有RuntimeException子类元素的通用列表”。
当你往这个列表中添加RuntimeException及其子类实例时,不管这个未知的父类型是RuntimeException本身、Exception还是Object,RuntimeException的子类实例都可以安全向上转型为对应的父类型,不会出现类型不匹配的问题,因此编译器不会拦截这类写入操作。反过来,如果你从这个列表中读取元素,编译器只能保证取出的元素是Object类型——因为你不知道这个未知父类型到底是什么,无法给出更精确的类型约束。
核心规则总结
你感受到的“规则颠倒”,本质是没有记住泛型通配符的PECS(Producer Extends, Consumer Super)设计逻辑:
- 只要使用
? extends T声明的容器,就只适合读取T类型元素(作为数据生产者),不适合写入任何非null元素 - 只要使用
? super T声明的容器,就只适合写入T及其子类元素(作为数据消费者),不适合读取元素的具体业务类型
内容的提问来源于stack exchange,提问作者avittoria

