Java 泛型和模板

Java Generics and templates

我有以下 Java class 定义:

import java.util.*;

public class Test {

static public void copyTo(Iterator<? extends Number> it, List<? extends Number> out) {
    while(it.hasNext())
        out.add(it.next());
}
public static void main(String[] args) {
    List<Integer> in = new ArrayList<Integer>();
    for (int i = 1; i <= 3; i++) {
        in.add(i);
    }
    Iterator<Integer> it = in.iterator();
    List<Number> out = new ArrayList<Number>();
    copyTo(it, out);
    System.out.println(out.size());
}

}

就是这样,我在Java中使用wildcards定义了方法copyTo。我定义 List<Number> outIterator<Integer> it。我的想法是我可以将迭代器定义为 Iterator<? extends Number> 并且这将匹配类型。然而事实并非如此:

Test.java:13: error: no suitable method found for add(Number)
            out.add(it.next());
               ^
    method List.add(int,CAP#1) is not applicable
      (actual and formal argument lists differ in length)
    method List.add(CAP#1) is not applicable
      (actual argument Number cannot be converted to CAP#1 by method invocation conversion)
    method Collection.add(CAP#1) is not applicable
      (actual argument Number cannot be converted to CAP#1 by method invocation conversion)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Number from capture of ? extends Number
1 error

所以我继续为 copyTo 方法定义了另一个定义:

static public void copyTo(Iterator<? super Integer> it, List<? super Integer> out) {
        while(it.hasNext())
            out.add(it.next());
    }

也不行。在这种情况下使用 wildcards 的正确说法是什么?

如果一个方法签名涉及两个或多个通配符,而你的方法逻辑要求它们相同,你需要使用泛型类型参数而不是通配符。

static public <T extends Number> void copyTo(Iterator<? extends T> it, List<? super T> out) {
    while(it.hasNext())
        out.add(it.next());
}

这里我用到了PECS(producer extends,consumer super)。 out 消耗 Ts(所以 super),而迭代器产生 Ts,所以 extends.

编辑

正如@Cinnam 在评论中正确指出的那样,您可以不用

static void copyTo(Iterator<? extends Integer> it, List<? super Integer> out)

这些签名实际上是等价的,因为 Integer 是最终的,所以任何 class 是某些 class 扩展 Integer 的超级 class 必须是Integer 的超级 class。

但是,就编译器而言,这两个签名并不等同。你可以通过尝试

来测试这个
static <T extends Number> void copyTo1(Iterator<? extends T> it, List<? super T> out) {
    copyTo2(it, out); // doesn't compile
}

static void copyTo2(Iterator<? extends Integer> it, List<? super Integer> out) {
   copyTo1(it, out);
}

这个编译,说明就编译器而言,带类型参数的版本更通用

首先你想通过向方法本身添加一个类型变量来施加约束,因为使用通配符你不能在两个参数之间施加约束,然后你必须考虑涉及的类型的变化你的方法:

  • 你想要一个 Iterator<X> 作为输入,其中 X 至少是你想要复制的数字类型的类型(或子类型)
  • 你想要一个列表作为输出,其中 Y 最多是数字类型(或超类型)的类型

这些约束是不同的,必须以不同的方式表达:

static public <T> void copyTo(Iterator<? extends T> it, List<? super T> out) {
while(it.hasNext())
    out.add(it.next());
}

基本上就是"I accept an Iterator of T or a subtype of T and I output to a List of T or a supertype of T"