通配符

通配符(?)通常用于表示未知类型。通配符可用于各种情况:

  • 作为参数,字段或局部变量的类型
  • 作为返回类型

在泛型中,通配符不用于泛型方法调用,泛型类实例创建或超类型的类型参数。

通配符的分类

  • 非限定通配符

<?>表示了非限定通配符,<?>可以用任意类型来替代。

  • 限定通配符
    限定通配符对类型进行了限制。有两种限定通配符:
    1.<? extends T>它通过确保类型必须是T及T的子类来设定类型的上界
    2.<? super T>它通过确保类型必须是T及T的父类设定类型的下界

泛型类型必须用限定的类型来进行初始化,否则会导致编译错误。

上限有界通配符

可以使用上限通配符来放宽对变量的限制。
例如,要编写一个适用于List<Integer>List<Double>List<Number>的方法,可以通过使用上限有界通配符来实现这一点。

  1. public static double sumOfList(List<? extends Number> list) {
  2. double s = 0.0;
  3. for (Number n : list)
  4. s += n.doubleValue();
  5. return s;
  6. }
  7. // 例一 指定类型为List<Integer>
  8. List<Integer> li = Arrays.asList(1, 2, 3);
  9. System.out.println("sum = " + sumOfList(li));//输出结果:sum = 6.0
  10. // 例二 指定类型为List<Double>
  11. List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
  12. System.out.println("sum = " + sumOfList(ld));//输出结果:sum = 7.0

无界通配符

无界通配符类型通常用于定义未知类型,比如List<?>

无界通配符通常有两种典型的用法
1. 需要使用Object类中提供的功能实现的方法

  1. public static void printList(List<Object> list) {
  2. for (Object elem : list)
  3. System.out.println(elem + " ");
  4. System.out.println();
  5. }

printList只能打印一个Object实例列表,不能打印List<Integer>List<String>List<Double>等,因为它们不是List<Object>的子类型。

2. 当代码使用泛型类中不依赖于类型参数的方法

例如,List.size或List.clear。实际上,经常使用Class<?>,因为Class<T>中的大多数方法都不依赖于T。

  1. public static void printList(List<?> list) {
  2. for (Object elem: list)
  3. System.out.print(elem + " ");
  4. System.out.println();
  5. }
  6. // 因为List<A>是List<?>的子类,因此可以打印出任何类型
  7. List<Integer> li = Arrays.asList(1, 2, 3);
  8. List<String> ls = Arrays.asList("one", "two", "three");
  9. printList(li);
  10. printList(ls);

因此,要区分场景来选择使用List<Object>或是List<?>。如果想插入一个Object或者是任意Object的子类,就可以使用List<Object>
不能在List<?>插入个Object或者是任意Object的子类,只能在List<?>中插入null。

下限有界通配符

下限有界通配符将未知类型限制为该类型的特定类型或超类型。
使用下限有界通配符语法为<? super A>

假设要编写一个将Integer对象放入列表的方法。为了最大限度地提高灵活性,希望该方法可以处理List<Integer>List<Number>或者是List<Object>等可以保存Integer值的方法。

  1. // 将数字1到10添加到列表的末尾
  2. public static void addNumbers(List<? super Integer> list) {
  3. for (int i = 1; i <= 10; i++) {
  4. list.add(i);
  5. }
  6. }

多个边界

类型参数其实是可以有多个边界的。
具有多个边界的类型变量是绑定中列出的所有类型的子类型。

  1. <T extends B1 & B2 & B3>
  2. // 如果其中一个边界是类,则必须首先指定它
  3. Class A { /* ... */ }
  4. interface B { /* ... */ }
  5. interface C { /* ... */ }
  6. class D <T extends A & B & C> { /* ... */ }
  7. // 如果未首先指定绑定A,则会出现编译时错误
  8. class D <T extends B & A & C> { /* ... */ } // compile-time error

:在有界类型参数中的extends,即可以表示“extends”(类中的继承)也可以表示“implements”(接口中的实现)。

通配符及其子类

可以使用通配符在泛型类或接口之间创建关系。

给定以下两个常规(非泛型)类:

  1. class A { /* ... */ }
  2. class B extends A { /* ... */ }
  3. // 此示例显示常规类的继承遵循此子类型规则:如果B扩展A,则类B是类A的子类型
  4. // ,但此规则不适用于泛型类型
  5. B b = new B();
  6. A a = b;
  7. List<B> lb = new ArrayList<>();
  8. List<A> la = lb; // compile-time error

鉴于Integer是Number的子类型,List<Integer>List<Number>之间的关系是什么?下图显示了List<Integer>List<Number>的公共父级是未知类型List<?>

image.png

尽管Integer是Number的子类型,但List<Integer>并不是List<Number>的子类型。

为了在这些类之间创建关系以便代码可以通过List<Integer>的元素访问Number的方法,需使用上限有界通配符

  1. List<? extends Integer> intList = new ArrayList<>();
  2. List<? extends Number> numList = intList; // OK. List<? extends Integer> is a subtype of List<? extends Number>

因为Integer是Number的子类型,而numList是Number对象的列表,所以intList(Integer对象列表)和numList之间现在存在关系。下图显示了使用上限和下限有界通配符声明的多个List类之间的关系。

image.png