本文首发于掘金
List<?>
和 List<Object>
的区别
区别:
List<?>
变量的元素只能读,不能写(可以插入null
元素)List<Object>
不是List<String>
的父类,但List<?>
是所有List泛型的父类,因此List<?>
变量可以被赋值为多种List泛型类型
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
上面代码中定义的方法只能接受List<Object>
类型的变量为参数,不能接受List<String>
,改成List<?>
的话就可以接受了。
List<?>
和 List
的区别
List<?>
是所有List泛型的父类,而List
是List泛型的原类型(raw type)。允许使用List原类型是为了兼容引入泛型前的代码,使用原类型会失去泛型的安全性和表达性,可能在运行时报错,还需要自己转型。因此编译器会给出警告。
注意,有一些需要使用原类型的场景:
- 在类的字面量中必须使用原类型,
List.class
、String[].class
和int.class
是正确的,但List<?>.class
List<String>.class
是错误的。 - instanceof 操作符中使用原类型就够了,因为泛型信息会在运行时擦除,在 instanceof 操作符中只能使用无界通配符类型或原类型,而且两者并没有区别,推荐后者。
// instanceof 操作符的推荐使用方式
if (o instanceof Set) {
Set<?> s = (Set<?>) o; // 这种转型时有检查的,不会有编译时警告
}
<?>
的使用场景
- 用来读取的List变量(in),使用extend定义下界;能使用
<Object>
通配符的地方,就改用无界通配符<?>
- 用来写入的List变量(out),使用super定义上界
- 当List变量同时需要in-out访问时,不使用通配符
例如JDK源码中List接口的的containsAll、removeAll方法都是用<?>
定义的:
boolean containsAll(Collection<?> c);
boolean removeAll(Collection<?> c);
而addAll方法,虽然也是读取,但是在后续使用时,会调用E的方法,因此不能使用<Object>
通配符。
boolean addAll(Collection<? extends E> c);
参考备注
- 参考了Oracle Java文档 Unbounded Wildcards 和 Guidelines for Wildcard Use