Generics and Class.forName
I would like to create an instance of a specified class using its name. My code is shown below.
I get a compiler warning. Am I doing this the right way? Is it even possible to use the name of a class and get an instance of that type back, as I don't think there is any way of the compiler knowing what the type should be?
public static <T> T create(final String className) {
try {
final Class<?> clazz = Class.forName(className);
//WARNING: Type safety: Unchecked cast from capture#2-of ? to T
return (T) create(clazz);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static <T> T create(final Class<T> classToCreate) {
final Constructor<T> constructor;
try {
constructor = classToCreate.getDe开发者_高级运维claredConstructor();
final T result = constructor.newInstance();
return result;
}
catch (Exception e) {
e.printStackTrace();
}
}
Thanks
I think that the first method should look something like this:
public static <T> T create(final String className, Class<T> ifaceClass)
throws ClassNotFoundException {
final Class<T> clazz = Class.forName(className).asSubclass(ifaceClass);
return create(clazz);
}
You cannot do an up-cast typecast using a type parameter ... without those pesky type-safety warnings.
By the way, if you ignore those warnings, the create method may create an instance of some class that isn't compatible with the actual type used by the caller. This is likely to lead to an unexpected ClassCastException
later on; e.g. when the instance is assigned.
EDIT: @Pascal points out that we need to add a typecast to make this compile; i.e.
Class<T> clazz = (Class<T>) Class.forName(className).asSubclass(ifaceClass);
Unfortunately, we also need to add a @SuppressWarnings
annotation.
I think this is because Class.forName(..)
isn't parameterized for T. When you trigger the eclipse autocomplete, it assumes the clazz.newInstance() return Object. So, retain the cast and add @SuppressWarnings. If you don't use the method properly (i.e. String str = Utils.create("java.util.ArrayList");
) then a ClassCastException
will occur, but that is normal.
The second method is fine.
But for the first one, any class name could be passed as a parameter.
The point of the method would be to instanciate a class that the calling code doesn't know at compile-time, it only knows about it at runtime.
In these conditions, the calling code cannot set the Type Parameter, so the method cannot be parameterized, so the cast has no point...
Therefore, I would say the whole method has no point.
Some point could be given to the method if the calling code would know a supertype of the class. That could be passed as a parameter, and the cast would be possible and meaningful.
public static <T> T create(final Class<T> superType, final String className) {
try {
final Class<?> clazz = Class.forName(className);
final Object object = clazz.newInstance();
if (superType.isInstance(object)) {
return (T)object; // safe cast
}
return null; // or other error
} // catch and other error code
}
You can not restrict a type parameter to contain the type named className. Hence, a caller can supply any type to your create(String) function, which is of course not type safe.
Since you cannot statically enforce that the returned instance implements any interface (without having the caller tell you by passing the corresponding Class object), I'd dispense with generics, and have the caller cast to whatever type he expects.
public static Object create(final String className) {
try {
final Class<?> clazz = Class.forName(className);
return create(clazz);
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
A caller can then write:
Foo foo = (Foo) create("my.cool.FooBar");
as opposed to
Foo foo = create("my.cool.FooBar", Foo.class);
Even if you can get the code above to work, the newInstance() method of constructor assumes a zero argument constructor.
If the Class does not have one i.e the zero argument constructor of the Class you are trying to create has been explicitly declared private, or package depending on where the method is called from, you will get an IllegalAccessException. Something to add to your precondition, if you get it working.
I found an interesting thing: Type
can be converted to Class
directly if it is not a generic type. But i still can not Class.forName("java.util.List<SomeType>")
.
import java.lang.reflect.*;
import java.util.*;
public class Main {
public void func(List<List<String>> listlist, Map<String, List<String>> list, String s) {}
public static void main(String[] args) throws ClassNotFoundException {
Method m = Main.class.getDeclaredMethods()[1];
Type t0 = m.getGenericParameterTypes()[0];
boolean b0 = t0 instanceof Class;
boolean b01 = ((ParameterizedType)t0).getRawType() instanceof Class;
Type t1 = m.getGenericParameterTypes()[2];
boolean b1 = t1 instanceof Class;
}
}
精彩评论