26

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

dogbane
  • 266,786
  • 75
  • 396
  • 414

6 Answers6

40

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.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • 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 Nov 14 '09 at 09:50
  • @Esko, consider Google code (for released versions) and/or with Github (for actual code) – Thorbjørn Ravn Andersen Nov 14 '09 at 09:54
  • You need to add a cast to make it compile `final Class clazz = (Class) Class.forName(className).asSubclass(ifaceClass);` – Pascal Thivent Nov 14 '09 at 10:03
  • 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 Nov 14 '09 at 10:29
  • 1
    You can do it without unchecked casts as follows: `return ifaceClass.cast(create(Class.forName(className)))` – meriton Nov 14 '09 at 12:23
  • 9
    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 Nov 14 '09 at 12:26
  • @meriton Indeed, using `Class extends T>` as type for clazz is better. – Pascal Thivent Nov 14 '09 at 12:54
  • 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 Nov 29 '09 at 17:01
2

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
  • 588,226
  • 146
  • 1,060
  • 1,140
  • create will not throw a ClassCastException. The code calling it might, but not necessarily immediately. – meriton Nov 14 '09 at 12:47
  • 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 Nov 14 '09 at 13:36
2

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
  • 23,689
  • 4
  • 56
  • 62
  • Check out the method Class.cast(Object). – meriton Nov 14 '09 at 12:37
  • @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 Nov 14 '09 at 17:22
1

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
  • 68,356
  • 14
  • 108
  • 175
0

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.

Pi Delport
  • 10,356
  • 3
  • 36
  • 50
chrisg
  • 318
  • 3
  • 9
-1

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;
    }
}

enter image description here

osexp2000
  • 2,910
  • 30
  • 29