Java™ 教程(泛型通配符)

泛型通配符

在泛型代码中,称为通配符的问号()表示未知类型,通配符可用于各类状况:做为参数、字段或局部变量的类型,有时做为返回类型(尽管更好的编程实践是更加具体),通配符从不用做泛型方法调用、泛型类实例建立或超类型的类型参数。html

如下部分更详细地讨论通配符,包括上界通配符、下界通配符和通配符捕获。java

上界通配符

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

要声明一个上界通配符,请使用通配符('?'),后跟extends关键字,后跟上界,请注意,在此上下文中,extends在通常意义上用于表示“extends”(如在类中)或“implements”(如在接口中)。segmentfault

要编写适用于NumberNumber的子类型列表的方法,例如IntegerDoubleFloat,你能够指定List<?extends Number>List<Number>一词比List<? extends Number>更具限制性,由于前者只匹配Number类型的列表,然后者匹配Number类型或其任何子类的列表。api

考虑如下process方法:数组

public static void process(List<? extends Foo> list) { /* ... */ }

上界通配符<? extends Foo>,其中Foo是任何类型,匹配FooFoo的任何子类型,process方法能够像Foo类型同样访问列表元素:oracle

public static void process(List<? extends Foo> list) {
    for (Foo elem : list) {
        // ...
    }
}

foreach子句中,elem变量遍历列表中的每一个元素,如今能够在elem上使用Foo类中定义的任何方法。spa

sumOfList方法返回列表中数字的总和:code

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

如下代码使用Integer对象列表打印sum = 6.0htm

List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));

Double值列表可使用相同的sumOfList方法,如下代码打印sum = 7.0

List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));

无界通配符

使用通配符(?)指定无界通配符类型,例如List<?>,这称为未知类型的列表,有两种状况,无界通配符是一种有用的方法:

  • 若是你正在编写可使用Object类中提供的功能实现的方法。
  • 当代码使用泛型类中不依赖于类型参数的方法时,例如,List.sizeList.clear,事实上,常常使用Class<?>,由于Class<T>中的大多数方法都不依赖于T

考虑如下方法,printList

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

printList的目标是打印任何类型的列表,但它没法实现该目标 — 它只打印一个Object实例列表,它不能打印List<Integer>List<String>List<Double>等,由于它们不是List<Object>的子类型,要编写通用的printList方法,请使用List<?>

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

由于对于任何具体类型AList<A>List<?>的子类型,你可使用printList打印任何类型的列表:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
在本课程的示例中使用了 Arrays.asList方法,此静态工厂方法转换指定的数组并返回固定大小的列表。

重要的是要注意List<Object>List<?>是不同的,你能够将ObjectObject的任何子类型插入List<Object>,可是你只能在List<?>中插入null,通配符使用指南部分提供了有关如何肯定在给定状况下应使用哪一种通配符(若是有)的更多信息。

下界通配符

上界通配符部分显示上界通配符将未知类型限制为该类型的特定类型或子类型,并使用extends关键字表示,以相似的方式,下界通配符将未知类型限制为该类型的特定类型或超类型。

使用通配符(?)表示下界通配符,后跟super关键字,后跟下界:<? super A>

你能够指定通配符的上界,也能够指定下界限,但不能同时指定二者。

假设你要编写一个将Integer对象放入列表的方法,为了最大限度地提升灵活性,你但愿该方法能够处理List<Integer>List<Number>List<Object> — 任何能够保存Integer值的方法。

要编写适用于IntegerInteger超类型列表的方法,例如IntegerNumberObject,你能够指定List<? super Integer>List<Integer>一词比List<? super Integer>更具限制性,由于前者仅匹配Integer类型的列表,然后者匹配任何类型为Integer的超类型的列表。

如下代码将数字1到10添加到列表的末尾:

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

通配符和子类型

泛型、继承和子类型中所述,泛型类或接口不相关,仅仅由于他们的类型之间存在关系,可是,你可使用通配符在泛型类或接口之间建立关系。

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

class A { /* ... */ }
class B extends A { /* ... */ }

编写如下代码是合理的:

B b = new B();
A a = b;

此示例显示常规类的继承遵循此子类型规则:若是B扩展A,则B类是A类的子类型,此规则不适用于泛型类型:

List<B> lb = new ArrayList<>();
List<A> la = lb;   // compile-time error

假设IntegerNumber的子类型,List<Integer>List<Number>之间的关系是什么?

generics-listParent.gif

虽然IntegerNumber的子类型,但List<Integer>不是List<Number>的子类型,事实上,这两种类型不相关,List<Number>List<Integer>的公共父级是List<?>

为了在这些类之间建立关系以便代码能够经过List<Integer>的元素访问Number的方法,请使用上界的通配符:

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

由于IntegerNumber的子类型,而numListNumber对象的列表,因此intListInteger对象列表)和numList之间如今存在关系,下图显示了使用上界和下界通配符声明的多个List类之间的关系。

generics-wildcardSubtyping.gif


上一篇:类型推断

下一篇:泛型通配符捕获和Helper方法

相关文章
相关标签/搜索