Reputation: 2268
I am developing a framework for building generified menus for a Selenium testing framework, and I've been using Guava TypeToken to resolve the types of generic parameters, but now I've run into a problem where the type token doesn't resolve a parameter:
I have an abstract
base class
for a builder that generates a menu option:
public abstract class AbstractMenuOptionBuilder<O extends IClickable> {
protected final TypeToken<AbstractMenuOptionBuilder<O>> typeToken = new
TypeToken<AbstractMenuOptionBuilder<O>>(getClass()) { };
public abstract O create();
}
This is a concrete class
for a builder:
public class MenuOptionBuilder<O extends IClickable> extends AbstractMenuOptionBuilder<O> {
public O create() {
TypeToken<?> genericOptionParam = typeToken.resolveType(AbstractMenuOptionBuilder.class.getTypeParameters()[0]);
Class<O> optionClass;
try {
optionClass = (Class<O>) Class.forName(genericOptionParam.getType().getTypeName());
<.... snip ....>
} catch(ClassNotFoundException e) {
log.catching(e);
return null;
}
}
}
I have an abstract
base class
for menus which has a method to return a list of menu options:
public abstract class AbstractMenu<O extends IClickable> {
public final List<O> getOptions() {
//This is where my plan doesn't work. The runtime type is given by
//a concrete menu class which extends AbstractMenu, but that runtime
//type doesn't seem to pass through to the abstract base class for the builder.
MenuOptionBuilder<O> builder = new MenuOptionBuilder<O>(new MenuOptionBean()){};
<.... snip ....>
}
}
And I have a concrete menu class
that extends
it:
//The runtime type of 'Link' is not known by the type token that is supposed to
//resolve it in the abstract builder base class.
public SimpleMenu extends AbstractMenu<Link> {
<.... snip ....>
}
I was expected that the variable genericOptionParam
in MenuOptionBuilder
would resolve to Link
, but it doesn't Instead, it resolved to O
, the name of the generic type parameter instead of its runtime type of Link
. If I create an additional base class
like this, the generic parameter resolves correctly:
public abstract class AbstractSimpleLinkedMenu extends AbstractMenu<Link> {
public final List<Link> getOptions() {
MenuOptionBuilder<Link> builder = new MenuOptionBuilder<Link>(new MenuOptionBean()){};
<.... snip ....>
}
}
I would prefer not to have to add additional base classes like AbstractSimpleLinkedMenu
, so is there something I have missed or done incorrectly here? I thought that the anonymous inner class for the abstract builder would know the runtime type, expect that it doesn't if the builder is declared with a generic parameter. The runtime type is specified by the concrete menu class
, SimpleMenu
, but it doesn't seem to filter through to the abstract
builder class for menu options.
Upvotes: 0
Views: 1909
Reputation: 280132
That's how the TypeToken
"hack" works. It uses Class#getGenericSuperclass()
(or getGenericSuperInterface
). Its javadoc states
If the superclass is a parameterized type, the
Type
object returned must accurately reflect the actual type parameters used in the source code.
In this case, that is O
, here
public abstract class AbstractMenuOptionBuilder<O extends IClickable>
You get what is hard coded in the source code. If you hard code Link
as the type argument, as you do here
MenuOptionBuilder<Link> builder =
new MenuOptionBuilder<Link>(new MenuOptionBean()) {};
then you will get Link
.
In this case
MenuOptionBuilder<O> builder =
new MenuOptionBuilder<O>(new MenuOptionBean()){};
you've hard coded O
, so that's what you will get.
Here are some more things I've written on the subject of type tokens:
Upvotes: 4