在泛型代码中,称为通配符的问号(?
)表示未知类型,通配符可用于各类状况:做为参数、字段或局部变量的类型,有时做为返回类型(尽管更好的编程实践是更加具体),通配符从不用做泛型方法调用、泛型类实例建立或超类型的类型参数。html
如下部分更详细地讨论通配符,包括上界通配符、下界通配符和通配符捕获。java
你可使用上界通配符来放宽对变量的限制,例如,假设你要编写一个适用于List<Integer>
、List<Double>
和List<Number>
的方法,你能够经过使用上界通配符来实现这一点。编程
要声明一个上界通配符,请使用通配符('?
'),后跟extends
关键字,后跟上界,请注意,在此上下文中,extends
在通常意义上用于表示“extends”(如在类中)或“implements”(如在接口中)。segmentfault
要编写适用于Number
和Number
的子类型列表的方法,例如Integer
、Double
和Float
,你能够指定List<?extends Number>
,List<Number>
一词比List<? extends Number>
更具限制性,由于前者只匹配Number
类型的列表,然后者匹配Number
类型或其任何子类的列表。api
考虑如下process
方法:数组
public static void process(List<? extends Foo> list) { /* ... */ }
上界通配符<? extends Foo>
,其中Foo
是任何类型,匹配Foo
和Foo
的任何子类型,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.0
:htm
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.size
或List.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(); }
由于对于任何具体类型A
,List<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<?>
是不同的,你能够将Object
或Object
的任何子类型插入List<Object>
,可是你只能在List<?>
中插入null
,通配符使用指南部分提供了有关如何肯定在给定状况下应使用哪一种通配符(若是有)的更多信息。
上界通配符部分显示上界通配符将未知类型限制为该类型的特定类型或子类型,并使用extends
关键字表示,以相似的方式,下界通配符将未知类型限制为该类型的特定类型或超类型。
使用通配符(?
)表示下界通配符,后跟super
关键字,后跟下界:<? super A>
。
你能够指定通配符的上界,也能够指定下界限,但不能同时指定二者。
假设你要编写一个将Integer
对象放入列表的方法,为了最大限度地提升灵活性,你但愿该方法能够处理List<Integer>
、List<Number>
和List<Object>
— 任何能够保存Integer
值的方法。
要编写适用于Integer
和Integer
超类型列表的方法,例如Integer
、Number
和Object
,你能够指定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
假设Integer
是Number
的子类型,List<Integer>
和List<Number>
之间的关系是什么?
虽然Integer
是Number
的子类型,但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>
由于Integer
是Number
的子类型,而numList
是Number
对象的列表,因此intList
(Integer
对象列表)和numList
之间如今存在关系,下图显示了使用上界和下界通配符声明的多个List
类之间的关系。