The following generic Guice binding method behaves correctly:
<T> Key<?> bindMultibinder(
ArrayList<Class<? extends T>> contents, Class<T> superClass) {
Named annotation = randomAnnotation();
Multibinder<T> options =
Multibinder.newSetBinder(binder(), superClass, annotation);
for (Class<? extends T> t : contents) {
options.addBinding().to(t);
}
final Key<?> multibinderKey = Key.get(Types.setOf( superClass ), annotation);
return multibinderKey;
}
And uses client code like this:
ArrayList<Class<? extends Option>> options =
new ArrayList<Class<? extends Option>>();
options.add(CruiseControl.class);
bindMultibinder(options, Option.class);
However, if I want to allow Option take a generic parameter like Option<Radio>, then I assume I need to pass a TypeLiteral in the bindMultibinder superClass parameter. This is my best attempt so far:
<T> Key<?> bindMultibinder(
ArrayList<TypeLiteral<? extends T>> contents, TypeLiteral<T> superClass) {
Named annotation = randomAnnotation();
Multibinder<T> options =
Multibinder.newSetBinder(binder(), superClass, annotation);
for (TypeLiteral<? extends T> t : contents) {
options.addBinding().to(t);
}
final Key<?> multibinderKey = Key.get(Types.setOf(superClass.getRawType()), annotation);
return multibinderKey;
}
The binding code equivalent to the prior case looks like this:
ArrayList<TypeLiteral<? extends Option>> options =
new ArrayList<TypeLiteral<? extends Option>>();
options.add(new TypeLiteral<CruiseControl>(){});
bindMultibinder(options, new TypeLiteral<Option>(){});
I’m almost certain that the below binding is incorrect, because Types.setOf(superClass.getRawType()) returns a ParameterizedType
final Key<?> multibinderKey =
Key.get(Types.setOf(superClass.getRawType()), annotation);
Any ideas how to create the set correctly?
ParameterizedTypeis the java class that is used to represent types that in java source code you need to write with angle brackets: types likeFoo<Bar>orSet<Option>orSet<Option<Radio>>or evenSet<? extends Option<Radio>>. That is the return value you want.What you’ve done will in fact work correctly with the very minor change that you want to call
superClass.getType()in your next-to-last line instead ofsuperClass.getRawType(). That being said, while I’m here I do have a few other suggestions.First off, in your first method I’d change it to:
That will let you do calls like this:
Or, if you aren’t using guava – though you should be – you can use
Arrays.asListinstead ofImmutableList.of. You get the same amount of type safety as before without needing all those angle bracket declarations in your binding code.If you don’t have many callers of
bindMultibinderyet, I’d also swap the order of the arguments, but that might just be a personal style thing.With these same changes, your second method becomes:
And you can use it similarly:
Though thinking about it now, I wonder if you really want an overload of
bindMultibinderthat takes aTypeLiteral. Wouldn’t you rather have one that takes aKeyinstead?After all, you can call this method in almost the same fashion:
Except that
Keyis easier to type thanTypeLiteral, and if you need to put in something that’s identified only by its annotation, that’s trivial to do:Now, is that
@Suppressmaking you nervous? Good instincts.Unfortunately, the sad fact is that when dealing with reflection around generified types – types with angle brackets in them – you’re almost certainly going to have small unchecked bits. My suggestion is that you make the bit that needs to have untyped warnings suppressed as small as possible, and expose to the outside world as much type information as you can. If you return a
Key<?>from here, you’re likely going to make the caller of this method suppress untyped warnings when they try to use your return value. Better to do it here, where you can limit the warnings suppression to a single line, and one where you can prove that the cast is safe.