我编写了这个方便的通用函数,用于将集合集合转换为单个集合:
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>>)
现在List
是 Collection
,而String
是 T
。那么为什么这不起作用,我该如何解决呢?
答案 0 :(得分:8)
您的签名应该是:
public static <T> Set<T> makeSet(Collection<? extends Collection<T>> coll);
基本上List<S>
不是List<T>
的子类型只是因为S
是T
的子类型。该属性称为 covariance ,在Java中,泛型类型不协变(其他语言,如scala包含协变泛型类型)。< / p>
您所做的工作无效,因为应该可以将任何Collection<T>
添加到Collection<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>)
:< / p>
boolean addAll(Collection<? extends T> coll)
答案 1 :(得分: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;
}
答案 2 :(得分:4)
不,不是。
我会将声明更改为
public static <T> Set<T> makeSet(Collection<? extends Collection<T>> a_collection) {
....
}
只有当类型参数相同(或使用通配符)时,两个泛型类型才能是子类型,因此Collection<String>
不是Collection<Object>
的子类型。请检查Subtyping section of Generics tutorial。
答案 3 :(得分:3)
这是更广义问题的专用版本,“Collection<Circle>
是Collection<Shape>
吗?”
答案是(也许是令人惊讶的) no 。
在C++ FAQ的C ++上下文中陈述了这种推理。这是一个一般的OO问题,因此适用相同的一般推理。
例如,考虑一个替代的Universe,其中Collection<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添加到形状集合中会发生什么,这实际上是一个圆形集合?没有好的答案。
同样的推理适用于Collection<List<T>>
和Collection<Collection<T>>
。 Collection<List<T>>
不是Collection<Collection<T>>
,因为Collection<Collection<T>>
不是substitutable。可以将Queue<T>
添加到集合集合中,但不能将其添加到List<T>
的集合中。
答案 4 :(得分:2)
我几乎不想发布正确的答案,因为它太难看了,但由于三个顶级答案都错过了这个,我感到被迫。
public static <T> Set<T> makeSet(
Collection<? extends Collection<? extends T>> coll)
你读得对。两个“延伸”的。否则,你不能把List&lt; Integer&gt;和列表&lt; Double&gt;一起得到一个Set&lt; Number&gt;,这在逻辑上应该是可能的。
一旦你将泛型嵌入到泛型中,事情就会变得很糟糕。
您可以完全原谅选择更简单的答案。 :)只要知道它在逻辑上应该不会一直有效。
顺便说一句,使用Google Collections,您可以Iterables.concat(...)
执行此操作,或ImmutableSet.copyOf(Iterables.concat(...))
如果您需要重复数据删除。
答案 5 :(得分:0)
这是他开发Java 1.0以简化所有正在发生的愚蠢的C ++模板的东西。您添加了5层复杂化,以避免从一组对象中的一个愚蠢的强制转换为您的特定实例集。好的,如果你发现你在整个地方投掷并使你的代码变得丑陋,但实际上我敢打赌这种情况发生在500k行代码中。雅,我们能找到这些技术细节是件好事,但是当你开始沿着这条路走下去时,你的代码是否真的变得更易于维护?