为何内联传入泛型不匹配参数时函数调用仍能编译?
当一个函数返回List<Integer>这类泛型结果时,将其赋值给变量后再传入接受List<? super String>的函数会编译失败,但直接内联传入却能编译通过,这是怎么回事?
先看示例代码:
import java.util.Collections; import java.util.List; class ListTest { static void consumeList(List<? super String> lst) {}; static <T> List<T> createList(T value) { return Collections.singletonList(value); } void doIt() { List<Integer> ilst = createList(1); consumeList(ilst); // 编译失败,符合预期:"不兼容的类型: List<Integer>无法转换为List<? super String>" var ilst2 = createList(1); consumeList(ilst2); // 编译失败,符合预期:"不兼容的类型: List<Integer>无法转换为List<? super String>" consumeList(createList(1)); // 编译通过。原因是什么? } }
核心原因:目标类型驱动的泛型推断
前两个调用编译失败的原因
当你把createList(1)赋值给List<Integer>变量(或用var推断出List<Integer>)时,编译器会根据传入的参数1(Integer类型)直接推断泛型方法createList的类型参数T为Integer,最终得到List<Integer>类型的实例。
而List<Integer>和List<? super String>之间没有合法的子类型关系:Integer既不是String的超类型,也和String无继承关联,泛型的逆变规则(List<? super X>接受List<Y>当且仅当Y是X的超类型)在这里不适用,因此编译报错。
第三个调用编译通过的原因
当你直接内联调用createList(1)作为consumeList的参数时,编译器会根据目标方法的参数类型反向推断泛型方法的类型参数:
consumeList接受的是List<? super String>,这意味着它可以接受任何持有String超类型(比如Object)的列表。编译器会寻找一个合适的T,使得List<T>可以赋值给List<? super String>。
此时T被推断为Object:
- 参数
1是Integer类型,而Integer是Object的子类,完全符合createList的参数要求(T为Object时,参数可以是任何Object子类); - 返回的
List<Object>满足List<? super String>的要求(因为Object是String的超类,根据泛型逆变规则,List<Object>是List<? super String>的子类型)。
这种基于目标类型的推断是Java 8及以后版本的特性,它允许编译器根据上下文调整泛型方法的类型参数,让代码更灵活。
为什么编译器不阻止这种情况?
这不是编译器的疏漏,而是完全符合泛型规则的合法推断:
List<Object>可以安全传递给接受List<? super String>的方法,因为该方法只能向列表中添加String或其子类型(String是Object的子类,添加操作完全安全);- 从
List<? super String>中读取的元素会被推断为Object,也符合类型安全要求。
内容的提问来源于stack exchange,提问作者Bernd Watermann

