Java中的无界通配符<?>在什么时候使用

1,973 阅读2分钟

List<?>List<Object> 的区别

区别:

  1. List<?>变量的元素只能读,不能写(可以插入null元素)
  2. 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原类型是为了兼容引入泛型前的代码,使用原类型会失去泛型的安全性和表达性,可能在运行时报错,还需要自己转型。因此编译器会给出警告。

注意,有一些需要使用原类型的场景:

  1. 在类的字面量中必须使用原类型,List.classString[].classint.class 是正确的,但 List<?>.class List<String>.class 是错误的。
  2. instanceof 操作符中使用原类型就够了,因为泛型信息会在运行时擦除,在 instanceof 操作符中只能使用无界通配符类型或原类型,而且两者并没有区别,推荐后者。
// instanceof 操作符的推荐使用方式
if (o instanceof Set) {
    Set<?> s = (Set<?>) o; // 这种转型时有检查的,不会有编译时警告
}

<?> 的使用场景

泛型通配符的in-out使用规则中:

  1. 用来读取的List变量(in),使用extend定义下界;如果最多只调用Object类的方法,就改用无界通配符<?>
  2. 用来写入的List变量(out),使用super定义上界
  3. 当List变量同时需要in-out访问时,不使用通配符

例如JDK源码中List接口的的containsAll、removeAll方法都是用<?>定义的:

boolean containsAll(Collection<?> c);
boolean removeAll(Collection<?> c);

而addAll方法,虽然也是读取,但是在后续使用时,会调用E的方法,因此不能使用<Object>通配符。

boolean addAll(Collection<? extends E> c);

参考备注

  1. 参考了Oracle Java文档 Unbounded WildcardsGuidelines for Wildcard Use