24

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

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

Collection<T>我在 C# 中没有见过这样的东西,所以我想知道 Java和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

4

5 回答 5

29

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
    }
}
于 2012-05-27T23:07:15.303 回答
19

声明Collection<?>(发音为“未知集合”)是一个集合,其元素类型匹配任何东西,而Collection<T>代表一个类型的集合T

像往常一样,Angelika Langer 的泛型常见问题解答对该主题进行了广泛的讨论,这是完全理解 Java 中泛型的必读内容,尤其是无界通配符(这个问题的主题)。引用常见问题解答:

无界通配符看起来像“?”,代表所有类型的家族。无界通配符用作泛型类型实例化的参数。无界通配符在不需要了解参数化类型的类型参数的情况下很有用

有关更多技术细节,请查看Java 语言规范的§4.5.1 Type Arguments and Wildcards部分,其中指出:

类型参数可以是引用类型或通配符。通配符在只需要部分了解类型参数的情况下很有用。

于 2012-05-27T22:25:58.770 回答
5

Collection<T>你一起做

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

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

于 2012-05-27T22:28:57.500 回答
5

使用无界通配符 (?) 的那个实际上意味着? extends Object(任何扩展 Object 的东西)。

在 Java 中,这意味着只读性质,即允许我们从通用结构中读取项目,但不允许将任何内容放回其中,因为我们无法确定其中元素的实际类型它。

因此,我敢说在所printCollection讨论的方法中这是一种非常有效的方法,至少在我们遇到需要假设类型的情况之前。

如果必须在两者之间进行选择,我会说第二个(带有类型参数 T)是一种更简洁的方法,因为您至少可以假设集合具有某种类型T,并且在某些情况下可以证明是有用的。

例如,如果 Collection 需要同步,我可以简单地做:

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

我非常轻松地创建了一个新的 type 集合T,并将原始集合的所有元素复制到第二个集合中。我可以这样做,因为我可以假设集合的类型是T,而不是?

当然,我可以用第一种方法(使用无界通配符)做同样的事情,但它不是那么干净,我不得不假设集合的类型是对象,而不是?(不能有效地用作类型参数:)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);
    }
}
于 2012-05-27T23:52:33.093 回答
0

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

当然,我可以用第一种方法(使用 ubounded 通配符)做同样的事情,但它不是那么干净,我不得不假设 Collection 的类型是 Object,而不是 ? (不能有效地用作类型参数:new ArrayList<?>())。

于 2022-01-11T03:02:39.273 回答