Java泛型(有界通配符)

Java Generics (bounded wildcards)

根据Joshua Bloch的"有效Java"一书,有一个关于如何/何时使用泛型中的有界通配符的规则。这条规则是PECS(生产者扩展,Comsumer Super)。当我研究以下例子时:

1
2
3
Stack<Number> numberStack = new Stack<Number>();
Iterable<Integer> integers = ... ;
numberStack.pushAll(integers);

我知道这个规则非常适合这个例子。我必须将方法pushAll声明为以下示例:

1
2
3
4
5
6
7
// Wildcard type for parameter that serves as an E producer
public void pushAll(Iterable<? extends E> src) {
    for (E e : src)
    {
       push(e);
    }  
}

但是如果我有下面的例子会发生什么呢?

1
2
3
Stack<Integer> integerStack = new Stack<Integer>();
Iterable<Number> numbers = ... ;
integerStack.pushAll(numbers);

我必须按以下方式申报pushAll

1
2
3
4
5
6
public void pushAll(Iterable<? super E> src) {
    for (E e : src)
    {  
        push(e);
    }
}

根据PECS规则,上述声明是错误的。但我想得到一个StackIntegers,并把它传给这个StackNumber。为什么不做呢?为什么我总是使用extends关键字?为什么使用super是错误的?当然,这也代表了康萨姆的观点。为什么消费者应该一直是super

附:更具体地说,你可以在参考书的"第28项"部分找到上述例子。


当您声明一个Stack时,您的意思是一堆foo或foo的子类。例如,您希望能够将String放入Stack中。另一种方法是不正确的,您不能在Stack中插入另一个对象。

在您的示例中,您声明一个Stack。您应该能够在这个堆栈中放入整数,但不能放入其他数字(比如双精度数),如果声明参数,就可以这样做。这就是为什么put方法应该有一个类型的参数。


试图在堆栈中存储任意数字是不可能的,因为一个数字可能是另一个整数。所以你的例子没有多大意义。

当对象asts作为使用者时,即当对象的泛型类型的实例作为参数传递给对象的方法时,可以使用super。例如:

1
 Collections.sort(List<T>, Comparator<? super T>)

在本例中,sort方法从集合中获取T实例,并将它们作为参数传递给comparator的compare(T o1, T o2)

与您的第一个例子相比,在这个例子中,不可重复的src是一个生产者。pushAll()方法调用iterable的方法,它产生(即返回)t的实例。在这种情况下,iterable是一个生产者,因此使用? extends T


你的例子没有多大意义。像这样的结构意味着数字和每种类型都可以从数字中得到。所以定义一个上下边界,从类型编号到最具体的类型编号。另一方面,意味着允许使用数字及其任何超数。由于编号扩展了对象并实现了可序列化,因此允许以下三种类型:

  • java.lang.number号
  • java.lang.object对象
  • java.io.serializable可序列化
  • 在您的示例中,您声明了通用类型Stack。让我们考虑下面的内容。

  • 您的堆栈永远无法保存任何超类型整数的项
  • 您的堆栈永远无法保存任何整数子类型的项,因为整数类是最终的,因此它不能被子类化。
  • 因此,如果您想要声明通用类型Stack,那么您的iterable是Iterable类型,因此您的堆栈只能保存integer类型的项。你对助记符pecs完全正确,但这只在你选择了一个具体类型时有效,它至少有一个超级类型和至少一个子类型。


    首先要注意的是,整数扩展了数字,所以不应该将数字对象推到整数堆栈中。但是,第一个示例将处理整数、浮点数、bigdecimal和所有其他数字子类。


    pushAll方法中,您不传递类型E,而是传递扩展E的任何类型。因此,您可以通过扩展Number的任何类型的Iterable,而不是通过NumberIterable

    原始示例使用Number类型,因为您随后可以传递属于Number子类的任何类型,如IntegerBigDecimal等。

    在您的示例中,您是以另一种方式进行的。你用Integer申报你的Stack。因此,pushAll只能接受由Integer扩展的类。您将无法使用Numbers(或任何其他类,因为Integer是最终类)。