Java泛型:嵌套参数化类型传参报错原因及正确写法
这个问题本质是Java泛型中原始类型(Raw Type)和参数化类型的类型检查规则不一致,加上泛型默认不变性共同导致的。
第一个示例能通过编译的原因
第一个示例中test方法的入参是List list,这是完全没有指定泛型参数的原始类型。
Java在JDK5引入泛型时,为了兼容之前没有泛型的遗留代码,专门给原始类型做了特殊兼容:任何对应泛型类的参数化实例,都可以直接传递给原始类型引用,编译器仅抛出unchecked警告,不会阻断编译。因此List<Integer>可以直接传给原始类型List,编译正常通过。
第一个示例代码参考:
public static void main(String args[]) { List<Integer> integers = new ArrayList<>(); test(integers); } public static void test(List list) { // 原始类型入参,兼容所有List实例 }
第二个示例编译报错的原因
第二个示例中test方法的入参是List<List> lists,这个类型不是原始类型:外层List已经明确指定了泛型参数为List(内层List是原始类型),整个入参属于参数化类型,不享受原始类型的遗留兼容规则。
Java泛型默认具备不变性:对于泛型类型G<T>,如果两个参数化类型的泛型实参不是完全一致,二者就不存在类型兼容关系,不能互相传递,除非通过? extends/? super通配符显式声明协变/逆变。
这里方法要求的外层List泛型参数是原始类型List,而传入的实参泛型参数是List<Integer>,二者不是完全相同的类型,因此编译器直接报类型不匹配错误。
报错示例代码参考:
public static void main(String args[]) { List<List<Integer>> integers = new ArrayList<>(); test(integers); // 编译错误:'test(java.util.List<java.util.List>)' cannot be applied to '(java.util.List<java.util.List<java.lang.Integer>>)' } public static void test(List<List> lists) { // 参数化类型入参,泛型检查严格 }
常见误区:不要把
List<List>和原始类型List混淆,只要外层泛型类指定了任意泛型参数(哪怕参数本身是原始类型),整个类型就属于参数化类型,走严格泛型检查逻辑。
不要为了绕过编译检查直接把入参写成原始类型List lists,这会丢失所有泛型类型安全保障,运行时极易抛出ClassCastException。根据使用场景选以下两种标准写法即可:
- 仅读取列表内容、不做修改操作时,用无界通配符声明协变:
public static void test(List<? extends List<?>> lists) { // 可以安全遍历读取lists中的元素,元素类型为List<?> }
- 需要修改列表内容、或者需要保留内层列表泛型信息时,用泛型方法声明:
public static <T extends List<?>> void test(List<T> lists) { // 可以获取内层列表的泛型类型T,支持更灵活的写入、类型转换操作 }
内容的提问来源于stack exchange,提问作者JeSuisGrand

