The only way I was able to get the below generic method to work was to pass the seemingly redundant TypeLiteral<Set<T>>
parameter. I believe it should be possible to construct this parameter programmatically given the other parameter, but can't figure out how.
protected <T> Key<Set<T>> bindMultibinder(
TypeLiteral<Set<T>> superClassSet, TypeLiteral<T> superClass) {
final Key<Set<T>> multibinderKey = Key.get(superClassSet, randomAnnotation);
return multibinderKey;
}
Client code looks like:
bindMultibinder(new TypeLiteral<Set<A<B>>>(){}, new TypeLiteral<A<B>>(){});
Where A and B are interfaces.
If I try the following (removing the TypeLiteral<Set<T>> superClassSet
parameter), I get a java.util.Set<T> cannot be used as a key; It is not fully specified.
runtime error.
protected <T> Key<Set<T>> bindMultibinder(TypeLiteral<T> superClass) {
final Key<Set<T>> multibinderKey = Key.get(
new TypeLiteral<Set<T>>() {}, randomAnnotation);
return multibinderKey;
}
Please forgive me if you already know most of the answer: it's hard to make an assumption on your level.
The reason of the problem is type erasure, as you know already. In order to get rid of type erasure, Guice uses a trick with concrete ancestors, as shown below:
Point is, when you create a class which extends a generic superclass and explicitly specifies the type parameter, you will usually need to write metods which accept that very specific type, and these methods' signatures cannot be erased. In this case we have no problem discussed in FAQ, but compiler saves the type information, anyway: the users of your class will need to know the exact types in order to use the methods.
Now your version does not have a concrete class inherited from
TypeLiteral<Set<YourSpecificType>>
, it only hasTypeLiteral<Set<T>>
—and that's where it all fails.Changing my little example, that would be:
As you can see, our
GenericTest$1
is not concrete any more: it still have a type parameter, and its concrete value, hereString
, is lost during the compilation.You can of course avoid that, but in order to do so, you need to create a class with a specific type parameter used for inheritance—so that Guice would be able to work out the details. Wait a bit, I'll try to come up with an example.
Update: it turned out to be a VERY long bit. So here's an updated version for you:
As you can see, the type information is now preserved. I believe this approach is not used because it's way too painful even for this draft.
Fully specified means that the values of all type parameters are known. Constructing a fully specified
TypeLiteral<Set<T>>
from aTypeLiteral<T>
appears to be impossible using the Guice public API. Specifically,TypeLiteral
only has two constructors. The first is:This constructor attempts to deduce the values of type parameters from the
TypeLiteral
's runtime class. This will yield a fully specified type only if the runtime class determines the type parameter. However, because all instances of a generic class share the same runtime class (that is,new HashSet<String>().getClass() == new HashSet<Integer>().getClass()
, the type parameter is only known if a non-generic subclass ofTypeLiteral
is instantiated. That is, we can't reuse the same class declaration for different values ofT
, but must define a new class for eachT
. This is rather cumbersome, as alf's answer demonstrates.This leaves us with the other constructor, which is more helpful, but not part of the public API:
We can use this constructor as follows:
Testcase:
In a perfect world, Guice would offer a public API to accomplish this ...