Collection<?>和Collection<T>之间有什么区别?

25

我主要是一名C#开发者,我正在教授数据结构给我的朋友,但他们在大学使用Java,在Java中我看到了这样的表达式:

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

我在C#中没有见过这样的东西,所以我想知道Collection<T>和Java中的Collection<?>有什么区别?

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

我认为它也可以按照上面的方式编写。但是文档中的人在比较Collection<Object>Collection<T>

示例来自http://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html


你是指 void printCollection(Collection<T> c) 还是 <T> void printCollection(Collection<T> c)?后者是一种泛型方法。前者不是泛型方法,必须在具有类型参数 T 的泛型类中声明。 - newacct
5个回答

29

Collection<?> 是一个未知类型参数的集合。

对于调用者而言,Collection<?> 和其他集合没有区别。

void printCollection(Collection<?> c) { ... }

并且

<T> void printCollection(Collection<T> c) { ... }

然而,后者允许实现引用集合的类型参数,因此通常更受欢迎。

前一种语法之所以存在是因为在适当的范围内引入类型参数并非总是可能的。例如,请考虑:

List<Set<?>> sets = new ArrayList<>();
sets.add(new HashSet<String>());
sets.add(new HashSet<Integer>());

如果我用类型参数T替换?,那么sets中的所有集合都将被限制为相同的组件类型,也就是说,我不能再将具有不同元素类型的集合放入同一个列表中,如下面的尝试所示:

class C<T extends String> {
    List<Set<T>> sets = new ArrayList<>();

    public C() {
        sets.add(new HashSet<String>()); // does not compile
        sets.add(new HashSet<Integer>()); // does not compile
    }
}

1
我不确定我理解你上一个例子的意思。能否请你再详细阐述一下? - Tarik

19

声明 Collection<?>(读作“未知集合”)是一个元素类型可以匹配任何类型的集合,而 Collection<T> 表示类型为 T 的集合。

像往常一样,Angelika Langer 的 泛型 FAQ 对这个主题进行了广泛的讨论,必须阅读才能全面理解 Java 中的所有泛型,特别是无限制通配符(本问题的主题)。引用来自 FAQ 的话:

未限定通配符看起来像 " ? ",代表所有类型的家族。未限定通配符用作泛型类型实例化的参数。在不需要有关参数化类型的类型参数的知识的情况下,未限定通配符非常有用。

如需进一步了解技术细节,请查看Java语言规范§4.5.1类型参数和通配符部分,其中指出:

类型参数可以是引用类型或通配符。通配符在仅需要有关类型参数的部分知识的情况下非常有用。


我想知道其中一个是否比另一个更具性能优势。 - Tarik
9
由于类型擦除,在运行时,这两种形式完全等效,因此根本没有任何区别。 - Óscar López

5
使用 Collection<T>,您可以执行以下操作:
void printCollection(Collection<T> c) {
    for (T e : c) {
        System.out.println(e);
    }
}

使用 Collection<?> ,你只知道集合包含对象。


那么?使用 Collection<?>,你可以这样做:void printCollection(Collection<?> c) { for (Object e : c) { System.out.println(e); } } - newacct
那么为什么不使用 Object,因为我们需要在方法实现中使用 Object 呢? - Tarik

5
使用无界通配符(?)实际上意味着“? extends Object”(任何继承自Object的类型)。在Java中,这意味着只读性质,即我们可以从泛型结构中读取项目,但是我们不允许将任何东西放回其中,因为我们无法确定其中元素的实际类型。因此,在讨论的printCollection方法中,我敢说这是一种非常有效的方法,至少在遇到需要假定类型的情况之前是如此。如果必须在两者之间选择,我会说第二个(带有类型参数T)是更清晰的方法,因为您至少可以假定集合具有某种类型T,并且在某些情况下可能证明非常有用。例如,如果需要同步集合,我可以简单地执行:
<T> void printCollection(Collection<T> c) {
    List<T> copy = new ArrayList<T>();
    synchronized(c) {
        copy.addAll(c);
    }
    for (T e : copy) {
        System.out.println(e);
    }
}

我可以很容易地创建一个类型为 T 的新集合,并将原始集合的所有元素复制到这个第二个集合中。我之所以能这么做,是因为我可以假设集合的类型是 T 而不是 ?

当然,我也可以使用第一种方法(使用无界通配符)来完成相同的操作,但这样做并不太干净利落,我必须假设集合的类型是 Object,而不是 ?(不能有效地将其用作类型参数:new ArrayList<?>())。

void printCollection(Collection<?> c) {
    List<Object> copy = new ArrayList<Object>();
    synchronized(c) {
        copy.addAll(c);
    }
    for (Object e : copy) {
        System.out.println(e);
    }
}

0

我很容易创建一个新的T类型的集合,并将原始集合中的所有元素复制到这个第二个集合中。这是可以做到的,因为我可以假设集合的类型是T,而不是?。

当然,我也可以使用第一种方法(使用无界通配符)来做同样的事情,但它不够干净,我必须假设Collection的类型是Object,而不是?(不能作为类型参数有效地用于new ArrayList<?>())。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接