共计 3352 个字符,预计需要花费 9 分钟才能阅读完成。
泛型通配符
在泛型代码中,称为通配符的问号(?)表示未知类型,通配符可用于各种情况:作为参数、字段或局部变量的类型,有时作为返回类型(尽管更好的编程实践是更加具体),通配符从不用作泛型方法调用、泛型类实例创建或超类型的类型参数。
以下部分更详细地讨论通配符,包括上界通配符、下界通配符和通配符捕获。
上界通配符
你可以使用上界通配符来放宽对变量的限制,例如,假设你要编写一个适用于 List<Integer>、List<Double> 和 List<Number> 的方法,你可以通过使用上界通配符来实现这一点。
要声明一个上界通配符,请使用通配符(’?’),后跟 extends 关键字,后跟上界,请注意,在此上下文中,extends 在一般意义上用于表示“extends”(如在类中)或“implements”(如在接口中)。
要编写适用于 Number 和 Number 的子类型列表的方法,例如 Integer、Double 和 Float,你可以指定 List<?extends Number>,List<Number> 一词比 List<? extends Number> 更具限制性,因为前者只匹配 Number 类型的列表,而后者匹配 Number 类型或其任何子类的列表。
考虑以下 process 方法:
public static void process(List<? extends Foo> list) {/* … */}
上界通配符 <? extends Foo>,其中 Foo 是任何类型,匹配 Foo 和 Foo 的任何子类型,process 方法可以像 Foo 类型一样访问列表元素:
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
// …
}
}
在 foreach 子句中,elem 变量遍历列表中的每个元素,现在可以在 elem 上使用 Foo 类中定义的任何方法。
sumOfList 方法返回列表中数字的总和:
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:
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 类之间的关系。
上一篇:类型推断