通配符
通配符(?)通常用于表示未知类型。通配符可用于各种情况:
- 作为参数,字段或局部变量的类型
- 作为返回类型
在泛型中,通配符不用于泛型方法调用,泛型类实例创建或超类型的类型参数。
通配符的分类
- 非限定通配符
<?>表示了非限定通配符,<?>可以用任意类型来替代。
- 限定通配符
限定通配符对类型进行了限制。有两种限定通配符:
1.<? extends T>它通过确保类型必须是T及T的子类来设定类型的上界
2.<? super T>它通过确保类型必须是T及T的父类设定类型的下界
泛型类型必须用限定的类型来进行初始化,否则会导致编译错误。
上限有界通配符
可以使用上限通配符来放宽对变量的限制。
例如,要编写一个适用于List<Integer>
、List<Double>
和List<Number>
的方法,可以通过使用上限有界通配符来实现这一点。
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
// 例一 指定类型为List<Integer>
List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));//输出结果:sum = 6.0
// 例二 指定类型为List<Double>
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));//输出结果:sum = 7.0
无界通配符
无界通配符类型通常用于定义未知类型,比如List<?>
。
无界通配符通常有两种典型的用法
1. 需要使用Object类中提供的功能实现的方法
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>
的子类型。
2. 当代码使用泛型类中不依赖于类型参数的方法
例如,List.size或List.clear。实际上,经常使用Class<?>
,因为Class<T>
中的大多数方法都不依赖于T。
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
// 因为List<A>是List<?>的子类,因此可以打印出任何类型
List<Integer> li = Arrays.asList(1, 2, 3);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
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到10添加到列表的末尾
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
多个边界
类型参数其实是可以有多个边界的。
具有多个边界的类型变量是绑定中列出的所有类型的子类型。
<T extends B1 & B2 & B3>
// 如果其中一个边界是类,则必须首先指定它
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
class D <T extends A & B & C> { /* ... */ }
// 如果未首先指定绑定A,则会出现编译时错误
class D <T extends B & A & C> { /* ... */ } // compile-time error
注:在有界类型参数中的extends,即可以表示“extends”(类中的继承)也可以表示“implements”(接口中的实现)。
通配符及其子类
可以使用通配符在泛型类或接口之间创建关系。
给定以下两个常规(非泛型)类:
class A { /* ... */ }
class B extends A { /* ... */ }
// 此示例显示常规类的继承遵循此子类型规则:如果B扩展A,则类B是类A的子类型
// ,但此规则不适用于泛型类型
B b = new B();
A a = b;
List<B> lb = new ArrayList<>();
List<A> la = lb; // compile-time error
鉴于Integer是Number的子类型,List<Integer>
和List<Number>
之间的关系是什么?下图显示了List<Integer>
和List<Number>
的公共父级是未知类型List<?>
。
尽管Integer是Number的子类型,但List<Integer>
并不是List<Number>
的子类型。
为了在这些类之间创建关系以便代码可以通过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类之间的关系。