通配符对集合的使用方式进行了限制。
例如,使用List<? extends Number>,我无法将新元素添加到列表中。这是因为我只知道该列表是Number 的某种子类型,但我不知道实际的子类型是什么(所以我怎么知道要添加什么?)。比如下面的代码:
public void doSomethingWith(List<? extends Number> numbers) {
numbers.add(Integer.valueOf(0)); // Won't compile
}
这不会编译,因为这些方法调用都是合法的:
doSomethingWith(new ArrayList<Integer>());
doSomethingWith(new ArrayList<Double>());
您可以做的是读取列表中的元素:
// This will all compile
public void doSomethingWith(List<? extends Number> numbers) {
for (Number number : numbers) {
// Do something with number
}
// OR
Number number = numbers.get(0);
// OR
Number number = numbers.remove(0);
}
调用get 之类的方法将返回某种Number,我们知道这是因为? extends Number,所以我们可以这样对待它以供阅读。
另一方面,List<? super Integer> 的结果正好相反。我不能再从列表中读取,但我可以写入它。我知道无论? 是什么,它肯定是Integer 的超类,所以列表的具体类型肯定会接受Integer 值。例如:
public void doSomethingWith(List<? super Integer> integers) {
integers.add(Integer.valueOf(0));
}
该代码是完全合法的。但是,如果你想从列表中读取,唯一的方法是使用Object,因为其他任何东西都需要强制转换(这需要知道它的具体类型):
for (Object obj : integers)
// OR
Object obj = integers.get(0);
// OR
Object obj = integers.remove(0);
到底发生了什么
这是实际发生的事情。当您指定? extends Number 时,您正在使将 元素作为参数的任何方法都无法使用。事实上,如果您尝试在 Eclipse 中对 List<? extends Number> 使用 Ctrl+Space 自动完成代码,它会将 null 显示为 add 方法等中的参数类型。同时,返回元素的所有方法都保证至少返回某种Number,尽管你不会确切知道它实际上可能是Number的哪个子类。
当您指定? super Integer 时,您正在使任何将 元素作为参数的方法保证它们将接受Integer 值(以及Integer 的子类) )。这允许您调用像 add 这样的方法,因为您知道它们会接受 Integer 类型。同时,所有返回元素的方法都只保证返回something,但我们不知道是什么,所以所有返回元素的方法都只保证返回@987654348 @。
PECS 是一个很好的首字母缩略词来记住这一点,它的意思是“P生产者 Extends,Consumer S上级”。这意味着如果你想让你的列表给你一些东西,它是一个生产者,你应该使用extends。如果你希望你的列表接受你的东西,它是一个消费者,所以你使用super。请参阅this answer 了解更多信息。
但是如果我有一个没有边界的通配符呢?
两者兼得! <?> 限制您调用将泛型类型作为参数的方法并且 导致所有返回泛型类型的方法返回Object。这是因为我们不知道类型是什么。例如,List<?> 中的所有这些分配都是合法的:
List<?> list;
list = new ArrayList<Integer>();
list = new ArrayList<String>();
list = new ArrayList<MyClass>();
等等。