Joshua Bloch的《Effective Java》一书中有一条关于如何/何时在泛型中使用有界通配符的规则。这个规则就是PECS(生产者-扩展,消费者-超级)。当我学习下面的例子时:
Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);
我明白这个规则非常适合这个例子。我必须将方法pushAll
声明为以下示例:
// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
for (E e : src)
{
push(e);
}
}
但是如果我有下面的例子会发生什么呢?
Stack<Integer> integerStack = new Stack<Integer>();
Iterable<Number> numbers = ... ;
integerStack.pushAll(numbers);
我必须声明pushAll
如下所示:
public void pushAll(Iterable<? super E> src) {
for (E e : src)
{
push(e);
}
}
根据PECS规则,上述声明错误。但是我想要一个Integer
的Stack
然后传递给这个Stack
和Number
。为什么不去做呢?
为什么我总是使用extends
关键字?为什么使用super
是错误的?当然,从消费者的角度来看也是如此。为什么消费者总是super
?
PS:更具体地说,你可以在参考书的"第28项"部分找到上面的例子。
当你声明一个Stack<Foo>
时,你指的是Foo的堆栈,或者Foo的子类。例如,您希望能够将String
放在Stack<Object>
中。另一种方式是不正确的,你不应该能够插入另一个对象,在Stack<String>
.
在你的例子中,你声明了一个Stack<Integer>
。您应该能够在这个堆栈中放入整数,但不能放入其他数字(如Double),如果声明参数<? super E>
,则可以这样做。这就是为什么放置方法应该有一个类型为<? extends E>
的参数。
试图在Stack中存储任意数字是不可能的,因为数字可能不是整数。所以你的例子没有多大意义。
当对象作为消费者存在时,即当对象的泛型类型的实例作为参数传递给对象的方法时,您将使用super。例如:
Collections.sort(List<T>, Comparator<? super T>)
在本例中,sort方法从集合中获取T个实例,并将它们作为参数传递给比较器的compare(T o1, T o2)
。
与第一个示例形成对比,其中Iterable src
是一个生成器。pushAll()
方法调用Iterable的一个方法,该方法产生(即返回)t的实例。在这种情况下,Iterable是一个生产者,因此使用? extends T
在pushAll
方法中,您不是传递类型E
,而是任何扩展E
的类型。因此,您可以传递扩展了Number
类型的任何Iterable
,而不是传递Number
s类型的Iterable
。
原始示例使用Number
类型,因为您可以传递Number
子类的任何类型,如Integer
, BigDecimal
等。
在你的例子中,你是反过来做的。您正在使用Integer
来声明您的Stack
。因此,pushAll
将只能接受那些由Integer
扩展的类。您将无法使用Numbers
(或任何其他类,因为Integer
是最终类)。
首先要注意的是Integer扩展了Number,所以不应该将Number对象压入Integer堆栈中。然而,第一个示例将使用整数、浮点数、BigDecimal和所有其他Number子类。
你的例子没有多大意义。像<? extends Number>
这样的构造意味着Number和所有继承Number的类型都是允许的。所以你定义了一个上限和一个下限,从类型Number到最具体的一个。反过来说,<? super Number>
意味着Number和它的任何超类型都是允许的。由于Number扩展了Object并实现了Serializable,因此允许以下三种类型:
- java.lang.Number
- java . lang . object
- java.io.Serializable
在您的示例中,您声明了泛型Stack<Integer>
。让我们考虑以下问题:
- 你的Stack永远不能保存任何超类型的Integer 项
- 你的Stack永远不能保存Integer的任何子类型的项,因为Integer类是final,因此它不能被子类化。
Stack<Integer>
,你的可迭代对象的类型是Iterable<Integer>
,因此你的Stack只能保存Integer类型的项。您使用助记符PECS是完全正确的,但这仅在您选择了至少具有一个超类型和至少一个子类型的具体类型时才有效。