在 Java 中,通配符可以做什么而常规泛型不能做什么?

In Java, what can a wild card do that regular generics cannot do?

我是 Java 的新手。在 this 文档中,他们将此作为使用通配符的用例:

static void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

这是他们的解决方案:

static void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

但我可以在没有通配符的情况下做同样的事情:

static <T> void printCollection(Collection<T> c) {
    Iterator i = c.iterator();
    for (int k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

谁能告诉我一个简单的用例,其中常规泛型不起作用但通配符起作用?

更新:这里的答案When to use wildcards in Java Generics?没有告诉我们需要通配符。事实上恰恰相反。

T 代表该数据结构的通用类型。在你的最后一个例子中,你没有使用它,它不是一个实际类型(例如字符串),因为你没有使用它在这种情况下并不重要。

例如,如果您有一个 Collection 并试图将它传递给一个接受 Collection 的方法,那是可行的,因为类路径上没有类型 T,因此它被视为一个变量。如果您尝试将同一个 Collection 传递给接受 Collection 的方法,那将不起作用,因为您的类路径上有 String,所以它不是变量。

列表为例

  • List<?> 可以是 List<A> 的父级 class。

例如,

List<B> bList = new ArrayList<>(); // B is a class defined in advance
List<?> list = bList;

在这种情况下你永远不能使用 <T>

  • <?> 有通配符捕获。

这里,

  void foo(List<?> i) {
        i.set(0, i.get(0));
    }

上面的代码无法编译。您可以修复它:

    void foo(List<?> i) {
        fooHelper(i);
    }

    // wildcard can be captured through type inference.
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }

查看更多,http://docs.oracle.com/javase/tutorial/java/generics/capture.html

目前只能想到这两个,以后可能会更新。

通配符允许我们做的一件事是声明与特定类型参数无关的类型,例如 "list of any kind of list":

List<List<?>> listOfAnyList = ...;

listOfAnyList.add( new ArrayList<String>() );
listOfAnyList.add( new ArrayList<Double>() );

没有通配符这是不可能的:* 因为元素列表可能具有不同的类型。

如果我们尝试 capture 它,我们会发现我们做不到:

static <E> void m(List<List<E>> listOfParticularList) {}

m( listOfAnyList ); // <- this won't compile

通配符允许我们做的类型参数不能做的另一件事是设置下限。 (可以使用 extends 绑定声明类型参数,但不能使用 super 绑定。**)

class Protector {
    private String secretMessage = "abc";

    void pass(Consumer<? super String> consumer) {
        consumer.accept( secretMessage );
    }
}

假设 pass 被声明为 Consumer<String>。现在假设我们有一个 Consumer<Object>:

class CollectorOfAnything implements Consumer<Object> {
    private List<Object> myCollection = new ArrayList<>();

    @Override
    public void accept(Object anything) {
        myCollection.add( anything );
    }
}

问题是:我们无法将其传递给接受 Consumer<String> 的方法。声明 Consumer<? super String> 意味着我们可以传递任何接受 String 的消费者。 (另见 Java Generics: What is PECS?。)

大多数时候,通配符只是让我们做出整洁的声明。

如果我们不需要使用类型,则不必为其声明类型参数。


* 技术上也可以使用 raw type,但不鼓励使用原始类型。

** 我不知道为什么 Java 不允许 super 作为类型参数。 4.5.1. Type Arguments of Parameterized Types 可能暗示它与类型推断的限制有关:

Unlike ordinary type variables declared in a method signature, no type inference is required when using a wildcard. Consequently, it is permissible to declare lower bounds on a wildcard […].