views:

540

answers:

5

Hi,

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.getDeclaredConstructor();
        final T result = constructor.newInstance();
        return result;
    }
    catch (Exception e) {
        e.printStackTrace();
    }
}

Thanks

+3  A: 

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 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.

Stephen C
Yep, this is as close you can get with Java. As for the creation of the actual Object there's still a few tricks to be done but I'm not going to get into the specifics since those tricks are implemented in my utility class I'm planning on releasing as soon as I find out the proper channel to spread it among all Java programmers out there.
Esko
@Esko, consider Google code (for released versions) and/or with Github (for actual code)
Thorbjørn Ravn Andersen
You need to add a cast to make it compile `final Class<T> clazz = (Class<T>) Class.forName(className).asSubclass(ifaceClass);`
Pascal Thivent
Thorbjørn, that's not the problem, the problem is that I want to tell people about the utlity but haven't found an efficient way of doing just that.
Esko
You can do it without unchecked casts as follows: `return ifaceClass.cast(create(Class.forName(className)))`
meriton
Alternatively, if you want to use asSubclass rather than cast, you should declare clazz to be of type `Class<? extends T>`, removing the need for the unchecked cast.
meriton
@meriton Indeed, using `Class<? extends T>` as type for clazz is better.
Pascal Thivent
Thorbjørn: Okay, took a bit longer than I expected but as promised, here's the utility I was talking about: http://code.google.com/p/bean-property-controller/ It doesn't have all the tricks for instantiating I know of, however it can instantiate a bit more exotic objects to some extent already.
Esko
+1  A: 

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.

Bozho
create will not throw a ClassCastException. The code calling it might, but not necessarily immediately.
meriton
Well, yeah, but this depends on how he fixes his methods, because now they don't compile - either he rethrows the exception, or return null (which will cause NPE)
Bozho
+1  A: 

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
}
KLE
Check out the method Class.cast(Object).
meriton
(To get shorter code, and eliminate that unchecked warning)
meriton
@Meriton True, if the error chosen to report an impossible cast is to throw a ClassCastException. For example, this code returns null, a different option... A choice must be made... ;-)
KLE
A: 

Even if you can get the code above tho work, the newInstance() method of constructor assumes a zero argument constructor.

If the Class does not have one i.e the zero argment 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.

chrisg
A: 

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);
meriton