Is List <List <String>> an instance of Collection <Collection <T>>?

I wrote this convenient universal function to convert a collection of sets into one set:

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

Then I tried to call it:

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

and I got the following error:

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

Now a List is Collection , a String is T So why is this not working and how can I fix it?

+7
java collections generics covariance
Nov 20 '09 at 17:50
source share
6 answers
 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; } 
+7
Nov 20 '09 at 17:55
source share

Your signature should be:

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

In principle, List<S> not a subtype of List<T> just because S is a subtype of T This property is called covariance , and in Java generic types are not covariant (other languages ​​such as scala contain covariant generic types).

What you did did not work, because it should be possible to add any Collection<T> to Collection<Collection<T>> , so, for example, with your signature, this will be a valid implementation:

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

But then calling this method as follows:

 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 a HashSet 

So this leads to the same problem? NO! The reason is that the compiler will not allow you to add anything to a collection parameterized by an unknown type:

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

First of all, you had to have wildcards so that you could do things like what you wanted to do, probably best demonstrated how the Collection.addAll method was generalized to allow List<Number>.addAll(List<Integer>) :

 boolean addAll(Collection<? extends T> coll) 
+8
Nov 20 '09 at 17:53
source share

No, it is not.

I would change the ad as

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

Two typical types can only be subtypes if the type arguments are identical (or with wildcards, so Collection<String> not a subtype of Collection<Object> ). Check out the subtyping section of the General lesson .

+4
Nov 20 '09 at 17:53
source share

This is a specialized version of a more generalized question: "Is Collection<Circle> a kind of Collection<Shape> ?"

The answer is (perhaps surprising).

The rationale is well said in the context of C ++ in the C ++ FAQ . This is a general OO question, so the same general reasoning applies.

For example, consider an alternative universe, where a Collection<Circle> is a Collection<Shape> view. In this universe, you can do something like this:

 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. 

What happens when Square, Shape is added to a collection of shapes, which really is a set of circles? There is no good answer.

The same considerations apply to Collection<List<T>> and a Collection<Collection<T>> . A Collection<List<T>> not a view of Collection<Collection<T>> because it is not substitutable for Collection<Collection<T>> . A Queue<T> can be added to a collection of collections, but it cannot be added to a collection of List<T> .

+3
Nov 20 '09 at 17:55
source share

I almost hate posting the correct answer because it is so ugly, but since the three main answers have missed it, I feel compelled.

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

You read it right. Two "expands." Otherwise, you cannot put List <Integer> and List <Double> together to get Set <Number>, which should be logically possible.

Once you get to the generics nested inside the generics, things always get unpleasant.

You can completely forgive the choice instead of the simpler answer. :) Just know that it will not always work where it should logically be.

By the way, with the Google Collection, you can use Iterables.concat(...) for this or ImmutableSet.copyOf(Iterables.concat(...)) if you need to uninstall.

+2
Nov 20 '09 at 23:55
source share

It was he who led Java 1.0 to development in order to simplify all the dumb C ++ templates that were occurring. You add 5 difficulty levels to avoid one stupid throw from the collection of objects to the set of your specific instance. Well, if you find that you throw everywhere and make your code ugly, but in fact I am sure that this happens about once every 500k lines of code. Ya, ya, it’s good that we can find these technical details, but does your code really become more convenient when you start going this way?

0
Nov 20 '09 at 18:21
source share



All Articles