1

I'm struggling with determining the correct method signature, using generics, for a method which transforms a Collection of type A into a specified collection of type B. For example, I have an ArrayList of A, and I want the method to return a LinkedList of B. So far, I've come up with

<A, B, C extends Collection<B>> C mapCollection(
  final Iterable<A> source,
  final Class<B> destinationClass,
  final Class<C> collectionClass)

But, if I write the following code:

ArrayList<TypeA> listOfA = getList();
LinkedList<TypeB> listOfB = mapCollection(listOfA, TypeB.class, LinkedList.class);

The compiler warns about mapCollection returning LinkedList. Apparently, it uses the LinkedList.class argument to determine the type of C, instead of seeing that C extends Collection<B>?

It gets even more interesting if I do

List<TypeB> = mapCollection(listOfA, TypeB.class, LinkedList.class);

because then, the compiler gives a type mismatch error, cannot convert from ArrayList to List<T>. Interestingly enough, this only happens in Eclipse, javac is perfectly happy with this code. Of course, I have to use a @SuppressWarnings("unchecked") which I'd rather avoid.

Any thoughts on how to fix this? Or is this impossible?

Michel
  • 603
  • 6
  • 15
  • How is the class the contains this method declared? And where are you calling `mapCollection` from? Are you calling it from within an instance method of the same class? – Eran Nov 02 '16 at 09:54
  • `mapCollection` is actually defined in a separate class, containing mapper stuff, which is injected as a bean. So I'm calling it from an instance method of a different class. – Michel Nov 02 '16 at 09:56
  • Can you show a more complete example? For example `SomeType someVar = ...; someVar.mapCollection(...)`. It makes a difference if the class that contains the `mapCollection` method has generic type parameters a the class level. – Eran Nov 02 '16 at 09:59
  • If you want to return other type of collection then you can try simple logic to eg `Collection collect(Collection c){ return r_instance.addAll(c)?r_instance.addAll(c):yourCustomException}` or something like that – bananas Nov 02 '16 at 10:11

3 Answers3

2

As others have noted, you're running into limitations of Java's generics that are caused by erasure. Specifically, a class literal LinkedList.class has type Class<LinkedList>. It can only represent the raw type like LinkedList; it cannot represent a parameterized type such as LinkedList<TypeB>.

If you had an instance of Class<LinkedList<TypeB>> then it could work:

Class<LinkedList<TypeB>> clazz = ... ;
List<TypeB> listOfB = mapCollection(listOfA, TypeB.class, clazz);

Unfortunately it's clumsy and difficult to get such a class instance. You could cast through raw to get it:

@SuppressWarnings("unchecked")
Class<LinkedList<TypeB>> clazz = (Class<LinkedList<TypeB>>) (Class) LinkedList.class;

but this is hardly better than what you have now.

You might investigate techniques such as Guava's TypeToken class (still in beta as of Guava 20). For background see the original article "Super Type Tokens" from Neal Gafter and an update on its limitations.


The main problem with using a type token is that it relies on the ability of the callee (mapCollection in this case) to be able to create an instance of the destination class. By convention, Java's collections have a no-arg constructor, so this is simple enough to be called reflectively. But what if some customization is necessary, such as specifying an initial capacity, or a comparator for sorting? Dealing with these is very difficult when using type tokens.

The Java 8 approach is instead to pass a function -- a Supplier -- that creates the destination instance. This lets the caller specify the type, and also any specialized constructor arguments, while it lets the callee determine when creation occurs. To do this, you'd rewrite your method signature like so:

<A, B, C extends Collection<B>> C mapCollection(
    final Iterable<A> source,
    final Supplier<C> supplier) { ... }

and you'd call it with a lambda expression:

List<TypeB> listOfB = mapCollection(listOfA, () -> new LinkedList<TypeB>());

or with a method reference:

List<TypeB> listOfB = mapCollection(listOfA, LinkedList::new);

Note that the type parameters of LinkedList are correctly inferred in the method reference case.

As a bonus, the mapCollection implementation needn't deal with any reflection. All it needs to do is call supplier.get().

P.S. Don't use LinkedList.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • Ah, the Java 8 solution looks very elegant! Two more follow-up questions though! First: shouldn't it be `LinkedList::new` or something like that? And second: what's wrong with `LinkedList`? I generally use `LinkedList` when I don't need random access but only iteration. – Michel Nov 03 '16 at 09:09
  • Somehow, `LinkedList::new` just works. Apparently, the compiler is smart enough to understand what I mean. Excellent solution, finally allows me to get rid of all the `@SuppressWarnings`! – Michel Nov 03 '16 at 09:17
  • And to answer my other follow-up question as well, here http://stackoverflow.com/a/323889/3676968 is a good summary of why not to use `LinkedList` except in specialized circumstances. I used `LinkedList` because I was wary of the re-allocation of `ArrayList`, but actually it might be less of a problem than I thought. Especially as I now can initialize the new list with the correct capacity using the lambda expression! – Michel Nov 03 '16 at 09:23
  • @Michel Yes, the type inference works well for the method reference, so it's not necessary to specify the type parameter explicitly as in `LinkedList::new`. It also works for the lambda expression, where you can use the diamond operator: `() -> new LinkedList<>()`. – Stuart Marks Nov 03 '16 at 18:14
  • @Michel Sorry about the editorial comment about LinkedList. :-) It's a bit of a pet peeve for me. The answer you found is a good one. There are cases where it's preferable to use a LinkedList instead of an ArrayList or ArrayDeque, but they're very rare. – Stuart Marks Nov 03 '16 at 18:17
  • ah I didn't even try <>, and I've just committed everything! Oh well... And, don't apologize for the `LinkedList`, I've learned something so it was useful :) never really thought about the memory overhead being so big. – Michel Nov 04 '16 at 07:31
  • @Stuart Marks: considering how often we see questions here, using `LinkedList` for no good reason, that remark is justified. Apparently, the message needs to be spread more. – Holger Nov 04 '16 at 14:27
  • @Holger OK, thanks, I shall carry on with the crusade. :-) – Stuart Marks Nov 04 '16 at 15:54
0

Type erasure is getting in your way. Look at this question. You can't retain C bounded type parameters (extends Collection<B>) while defining it as a Class type parameter.

You can provide a C type directly:

<A, B, C extends Collection<B>> void mapCollection(
    final Iterable<A> source,
    final Class<B> destinationClass,
    final C collectionClassInstance);

This way C will comply with extends Collection<B>, but the method signature won't be so elegant.

You can also use the Reflection API to obtain the type parameter. Here's an example.

Community
  • 1
  • 1
nandsito
  • 3,782
  • 2
  • 19
  • 26
-1

The compiler warns you because the method returns LinkedList.class but you are expecting an instance of List<TypeB>. You cannot do this with generics in Java without compiler warnings. And you cannot do List<TypeB>.class, it just doesn't work. It is impossible.

However, following code is a valid case:

<A, B, C> Collection<B> mapCollection(
  final Iterable<A> source,
  final Class<B> destinationClass,
  final Class<C> collectionClass){...}
Collection<TypeB> = mapCollection(listOfA, TypeB.class, LinkedList.class);

And this is as far as you can get with generics.

mtyurt
  • 3,369
  • 6
  • 38
  • 57