views:

3462

answers:

4

Is it possible to reflectively instantiate a generic type in Java? Using the technique described here I get an error because class tokens cannot be generic. Take the example below. I want to instantiate some subclass of Creator that implements Creator. The actual class name is passed in as a command line argument. The idea is to be able to specify an implementation of Creator at runtime. Is there another way to accomplish what I'm trying to do here?

public interface Creator<T> {
 T create();
}
public class StringCreator implements Creator<String> {
 public String create() { return new String(); }
}
public class FancyStringCreator implements Creator<String> {
 public String create() { return new StringBuffer().toString(); }
}
public static void main(String[] args) throws Exception {
 Class<?> someClass = Class.forName(args[0]);
 /*ERROR*/Class<? extends Creator<String>> creatorClass = someClass.asSubclass(Creator.class);
 Constructor<? extends Creator<String>> creatorCtor = creatorClass.getConstructor((Class<?>[]) null);
 Creator<String> creator = creatorCtor.newInstance((Object[]) null);
}

Edit: I like Marcus' approach as being the most simple and pragmatic without circumventing the whole generics thing. I can use it in my situation because I can specify that the class passed must be a subclass of StringCreator. But as Ericson pointed out the generic information is still there at the type level, just not at the runtime level so it is still possible to reflectively examine whether a given class implements the correct generic type.

+3  A: 

You don't need that line. Nor do you need the constructor as you're just using the default one. Just instantiate the class directly:

public static void main(String[] args) throws Exception {
        Class<?> someClass = Class.forName(args[0]);
        Creator<String> creator = (Creator<String>) someClass.newInstance();
}

If you insist, you'll only be able to get halfway there:

public static void main(String[] args) throws Exception {
    Class<?> someClass = Class.forName(args[0]);
    Class<? extends Creator> creatorClass = someClass.asSubclass(Creator.class);
    Constructor<? extends Creator> creatorCtor = creatorClass.getConstructor((Class<?>[]) null);
    Creator<String> creator = (Creator<String>) creatorCtor.newInstance((Object[]) null);
}
sblundy
claz.newInstance() and ctor.newInstance() don't behave exactly the same when an error occurs. Class's newInstance method is older, and inconsistent with other reflective "method" invocations.
erickson
A: 

Not quite sure why you're using generics here.

The instantiation of the object using reflection would suggest a general use but presumably you're going to call create at some point and assign the result to a String, otherwise why use the generics to control the return type.

But if you wrote the following implementation of Creator:

public class IntegerCreator implements Creator<Integer> 
{
  public Integer create() 
  { 
    ...
  }
}

And passed it in as a argument you'd get a ClassCastException when calling create and assigning the result.

Nick Holt
+2  A: 

This will do what you are trying to do while providing type safety. There's no way to avoid an unchecked warning, but the type checking done here justifies its suppression.

  public static void main(String[] args)
    throws Exception
  {
    Class<? extends Creator<String>> clz = load(argv[0], String.class);
    Constructor<? extends Creator<String>> ctor = clz.getConstructor();
    Creator<String> creator = ctor.newInstance();
    System.out.println(creator.create());
  }

  public static <T> Class<? extends Creator<T>> load(String fqcn, Class<T> type)
    throws ClassNotFoundException
  {
    Class<?> any = Class.forName(fqcn);
    for (Class<?> clz = any; clz != null; clz = clz.getSuperclass()) {
      for (Object ifc : clz.getGenericInterfaces()) {
        if (ifc instanceof ParameterizedType) {
          ParameterizedType pType = (ParameterizedType) ifc;
          if (Creator.class.equals(pType.getRawType())) {
            if (!pType.getActualTypeArguments()[0].equals(type))
              throw new ClassCastException("Class implements " + pType);
            /* We've done the necessary checks to show that this is safe. */
            @SuppressWarnings("unchecked")
            Class<? extends Creator<T>> creator = (Class<? extends Creator<T>>) any;
            return creator;
          }
        }
      }
    }
    throw new ClassCastException(fqcn + " does not implement Creator<String>");
  }

The main restriction you have to adhere to is that a class in the hierarchy must specify the type parameter. For example class MyCreator implements Creator<String>. You can't use it with class GenericCreator<T> implements Creator<T>.

It doesn't currently handle the valid case where you create a new interface interface StringCreatorIfc extends Creator<String>, and have a class implement that. It could be enhanced to do that, but I'll leave that as an exercise for those inclined.

erickson
+2  A: 

The generic information is lost in runtime. There is no runtime equivalent of a Creator<String>.class. You could create a type between Creator and StringCreator which fixes the generic type:

public interface Creator<T> {
        T create();
}
public interface StringCreator extends Creator<String> { }
public class StringCreatorImpl implements StringCreator  {
        public String create() { return new String(); }
}
public class FancyStringCreator implements StringCreator  {
        public String create() { return new StringBuffer().toString(); }
}
public static void main(String[] args) throws Exception {
        Class<?> someClass = Class.forName(args[0]);
        Class<? extends StringCreator> creatorClass = someClass.asSubclass(StringCreator.class);
        Constructor<? extends StringCreator> creatorCtor = creatorClass.getConstructor((Class<?>[]) null);
        Creator<String> creator = creatorCtor.newInstance((Object[]) null);
}

But of course you lose a bit of flexibility, because you cannot use the following creator class:

public class AnotherCreator implements Creator<String> {
    public String create() { return ""; }
}
Markus
Not all generic information is lost at runtime; there is enough left to check for the necessary type information even without creating another interface. It's just that there's just not enough info left for the compiler to deduce the correctness.
erickson
You're right there. What I said (in a complicated way) is that there is no class object that represents Creator<String> in difference to Creator<Integer>. But your code snipped below seems like a reasonable way to solve the problem.
Markus