Selena
Selena

Reputation: 2268

How to instantiate a Class object for a parameterized class using reflection and Guava TypeToken

I'm trying to pull off some Guava TypeToken magic for a class which is parameterized with a generic type parameter, but I am getting a ClassNotFoundException when trying to instantiate it through the Class.forName(String) method.

public class EnumeratedMenu<E extends Enum<E>,T extends EnumeratedBean<E>> {

    private final TypeToken<EnumeratedMenu<E,T>> typeToken =
            new TypeToken<EnumeratedMenu<E,T>>(getClass()) { };

    private Class<T> getBean() {

        TypeToken<?> genericBeanParam = typeToken.resolveType(EnumeratedMenu.class.getTypeParameters()[1]);

        try {
            //This generates a ClassNotFoundException because of the generic type parameter that EnumeratedBean has.
            return (Class<T>)Class.forName(genericBeanParam.getType().getTypeName());
        } catch(ClassNotFoundException e) {
            log.error("Unable to find the class definition for {}", genericBeanParam.getType().getTypeName());
            log.catching(e);
        }
    }
}

I am getting this exception:

"Unable to find the class definition for EnumeratedBean<ContextMenuOption$ContextType>"

ContextMenuOption$ContextType is the runtime type of the generic enum parameter for EnumeratedBean. How do I get the class object I want?

Clarification:

I need to construct and instance of the class to use with a builder object that will construct a menu option like this below:

T optionBean = beanFactory.create(beanClass);

IEnumeratedMenuOptionBuilder<E,T> builder = new EnumeratedMenuOptionBuilder<>(optionBean);
builder.setDriver(getDriver()).setTimeoutInSeconds(getTimeoutInSeconds())
        .setEnumerationFunction(getEnumerationFunction());

Upvotes: 1

Views: 3150

Answers (2)

ColinD
ColinD

Reputation: 110094

What are you actually trying to do here?

You're never going to be able to get a Class<T> object that represents a generic type like EnumeratedBean<ContextMenuOption$ContextType>, because Class can only represent a raw type like EnumeratedBean.

It's possible that there's some other way to achieve the result you want, but it's not clear what that result you want is currently. For example, what do you want to use the Class you're returning for? Perhaps it's possible to do that without the intermediate Class object.

Edit:

Since it looks like what you wanted was to be able to create an instance of T, here's an example of how you could do it using TypeToken:

public class EnumeratedMenu<E extends Enum<E>, T extends EnumeratedBean<E>> {

  // Just get the type of T, that's what we care about here
  private final TypeToken<T> typeToken = new TypeToken<T>(getClass()) {};

  private T create() {
    try {
      // Using the raw type Class object for the token to
      // get at its default constructor.
      return typeToken.constructor(typeToken.getRawType().getConstructor())
          .invoke(null);
    } catch (Exception e) {
      // ...
    }
  }
}

Upvotes: 4

StaxMan
StaxMan

Reputation: 116580

This is impossible to do, as there is always just one single Class, regardless of parameterizations. This is how Java generics work with type erasure. For detailed explanation of how and why, you may want to read:

http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html

But as to instantiation you will simply need to use the Class you already have; and cast generic type signature the way you want.

This means, for example, that you can actually do:

List<String> strings = new ArrayList<String>();
List<Integer> numbers = (List<Integer>)(List<?>) strings;
numbers.add(Integer.valueOf(12));

without runtime exceptions. There is no real runtime information limiting contents; only implicit casting. You would, however, get a runtime exception if you tried:

String str = strings.get(0);

because while underlying List happily accepts any java.lang.Objects, access code has "hidden" cast, which will trigger exception.

So, instantiation may work something like:

List<String> s = (List<String>) ArrayList.class.newInstance();

but using whatever generic type you want.

EDIT:

And if Guava's TypeToken is needed, its javadocs suggest you should either get raw class with getRawType(), or perhaps directly create instance with constructor().

Upvotes: 2

Related Questions