在Java中,通配符可以做常规泛型无法做到的事情吗?

我是Java的新手。在本文档中,他们给出了使用通配符的用例:

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());
    }
}

有人可以向我展示一个简单的用例,其中常规泛型不起作用,但通配符将起作用吗?

更新:这里的答案 什么时候在Java泛型中使用通配符?不要告诉我们需要通配符。事实上,情况恰恰相反。


答案 1

通配符允许我们做的一件事是声明对特定类型参数不可知的类型,例如“任何类型列表的列表”

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

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

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

如果我们试图捕捉它,我们会发现我们不能:

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

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

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

class Protector {
    private String secretMessage = "abc";

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

假设被声明为采取.现在假设我们有一个:passConsumer<String>Consumer<Object>

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

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

问题是:我们不能把它传递给接受.声明意味着我们可以传递任何接受 .(另请参阅 Java 泛型:什么是 PECS?Consumer<String>Consumer<? super String>String

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

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


*从技术上讲,原始类型也可以,但不鼓励原始类型。

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

与方法签名中声明的普通类型变量不同,使用通配符时不需要类型推断。因此,允许在通配符上声明下限[...]。


答案 2

T 代表该数据结构的泛型类型。在最后一个示例中,您不使用它,并且它不是实际类型(例如 String),并且由于您不使用它,因此在这种情况下并不重要。

例如,如果您有一个 Collection 并尝试将其传递给接受 Collection 的方法,则此方法有效,因为类路径上没有类型 T,因此将其视为变量。如果尝试将同一个集合传递给接受集合的方法,则不起作用,因为类路径上有 String,因此它不是变量。


推荐