8

我编写了这个方便的通用函数,用于将集合集合转换为单个集合:

public static <T> Set<T> makeSet(Collection<Collection<T>> a_collection) {
    Iterator<Collection<T>> it = a_collection.iterator();
    Set<T> result = new HashSet<T>();
    while (it.hasNext()) {
        result.addAll(it.next());
    }
    return result;
}

然后我试着叫它:

    List<List<String>> resultLists = ... ;
    Set<String> labelsSet = CollectionsHelper.makeSet(resultLists);

我收到以下错误:

<T>makeSet(java.util.Collection<java.util.Collection<T>>) in CollectionsHelper 
cannot be applied to (java.util.List<java.util.List<java.lang.String>>)

现在 aList 是 a Collection, aString 是 a T。那么为什么这不起作用,我该如何解决呢?

4

6 回答 6

8

你的签名应该是:

public static <T> Set<T> makeSet(Collection<? extends Collection<T>> coll);

基本上List<S>不是的子类型List<T>只是因为S是 的子类型T。该属性称为协变,在 Java 中,泛型类型不是协变的(其他语言,如scala包含协变泛型类型)。

您所做的没有奏效,因为应该可以将 any 添加Collection<T>到 aCollection<Collection<T>>中,例如,使用您的签名,这将是一个有效的实现:

public static <T> Set<T> makeSet(Collection<Collection<T>> coll) {
    coll.add(new HashSet<T>());
    return null;
}

但随后调用此方法如下:

List<List<String>> outside = new LinkedList<List<String>>();
makeSet(outside); //actually this line will not compile!
List<String> oops = outside.get(0); //oh dear - it's a HashSet

那么这会导致同样的问题吗?不!原因是编译器不允许您将任何内容添加到以未知类型参数化的集合中:

public static <T> Set<T> makeSet(Collection<? extends Collection<T>> coll) {
    coll.add(new HashSet<T>()); //this line will not compile
    return null;
}

首先有通配符是必要的,这样你就可以做你想做的事情,最好的证明是Collection.addAll方法是如何被泛化的,这样List<Number>.addAll(List<Integer>)就可以了:

boolean addAll(Collection<? extends T> coll)
于 2009-11-20T17:53:14.500 回答
7
public static <T> Set<T> makeSet(Collection<? extends Collection<T>> a_collection) {
    Iterator<? extends Collection<T>> it = a_collection.iterator();
    Set<T> result = new HashSet<T>();
    while (it.hasNext()) {
            result.addAll(it.next());
    }
    return result;
}
于 2009-11-20T17:55:48.713 回答
4

不,不是。

我会将声明更改为

public static <T> Set<T> makeSet(Collection<? extends Collection<T>> a_collection) {
    ....
}

只有当类型参数相同时,两个泛型类型才能成为子类型(或使用通配符,因此Collection<String>不是 的子类型Collection<Object>。检查泛型教程的子类型部分

于 2009-11-20T17:53:08.170 回答
3

这是更普遍的问题“是Collection<Circle>一种Collection<Shape>吗?”的专门版本。

答案是(也许令人惊讶)

推理在C++ 常见问题解答中的 C++ 上下文中得到了充分说明。这是一个一般的 OO 问题,所以同样的一般推理也适用。

例如,考虑一个替代宇宙,其中 aCollection<Circle> 一种Collection<Shape>。在这个宇宙中,你可以这样做:

Collection<Circle> circles = new Collection<Circle>();
Collection<Shape> shapes = circles; // OK, we're in an alternate universe
shapes.Add(new Circle()); // OK, we're adding a circle to a collection of circles
shapes.Add(new Square()); // Asplode!  We just added a square to a collection of circles.

如果将 Square(一个 Shape)添加到形状集合(实际上是圆的集合)中会发生什么?没有好的答案。

相同的推理适用于 aCollection<List<T>>和 a Collection<Collection<T>>。ACollection<List<T>>不是 kind-ofCollection<Collection<T>>因为它不能替代a Collection<Collection<T>>。AQueue<T>可以添加到集合的集合中,但不能添加到 的集合中List<T>

于 2009-11-20T17:55:26.200 回答
3

我几乎讨厌发布正确的答案,因为它太丑了,但是由于三个顶级答案都错过了这个,我感到被迫。

public static <T> Set<T> makeSet(
    Collection<? extends Collection<? extends T>> coll)

你没看错。两个“?扩展”。否则,您不能将 List<Integer> 和 List<Double> 放在一起得到 Set<Number>,这在逻辑上应该是可能的。

一旦你找到嵌套在泛型中的泛型,事情总是会变得很糟糕。

您完全可以原谅选择更简单的答案。:) 只要知道它并不总是在逻辑上应该起作用。

顺便说一句,您可以使用Google CollectionsIterables.concat(...)来执行此操作,或者ImmutableSet.copyOf(Iterables.concat(...))如果您需要重复数据删除。

于 2009-11-20T23:55:11.030 回答
0

这就是导致 Java 1.0 被开发以简化所有正在发生的愚蠢 C++ 模板的东西。您添加了 5 层复杂性,以避免从一组对象到一组特定实例的愚蠢转换。好吧,如果你发现你到处都在铸造并且让你的代码变得丑陋,但我敢打赌这种情况大约在 500k 行代码中发生一次。是的,是的,我们可以找到这些技术细节很好,但是当你开始走这条路时,你的代码真的变得更易于维护了吗?

于 2009-11-20T18:21:56.173 回答