泛型和Class.forName

26
我想通过类名创建一个实例。以下是我的代码。
我收到了编译器的警告。我这样做对吗?使用类的名称并获取该类型的实例是否可能,因为我认为编译器无法知道该类型应该是什么?
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();
    }
}

谢谢

6个回答

41
我认为第一种方法应该类似于这样:

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

您不能使用类型参数进行向上转型,否则会出现烦人的类型安全警告。

顺便说一下,如果您忽略这些警告,create方法可能会创建一个与调用者实际使用的类型不兼容的类的实例。这很可能会导致意外的ClassCastException,例如当实例被分配时。


编辑:@Pascal指出我们需要添加一个类型转换才能使其编译,即:

Class<T> clazz = (Class<T>) Class.forName(className).asSubclass(ifaceClass);

不幸的是,我们还需要添加一个@SuppressWarnings注释。


没错,这就是你可以用Java实现的最接近的方法了。至于实际对象的创建,还有一些技巧需要完成,但我不会详细介绍,因为这些技巧已经在我的实用类中实现了。我计划尽快找到适当的渠道将其传播给所有Java程序员。 - Esko
@Esko,考虑使用Google Code(用于发布版本)和/或Github(用于实际代码)。 - Thorbjørn Ravn Andersen
Thorbjørn,那不是问题,问题在于我想告诉人们这个实用工具,但还没有找到一个有效的方法来做到这一点。 - Esko
1
您可以按以下方式进行无未检查的转换:return ifaceClass.cast(create(Class.forName(className))) - meriton
9
如果您想使用asSubclass而不是cast,您应该声明clazz的类型为Class<? extends T>,这样就不需要进行未经检查的转换。 - meriton
显示剩余3条评论

2
第二种方法很好。
但是对于第一种方法,任何类名都可以作为参数传递。
该方法的重点是在运行时实例化调用代码不知道的类,而不是在编译时知道它。
在这些情况下,调用代码无法设置类型参数,因此该方法无法被参数化,因此强制转换没有意义...
因此,我认为整个方法没有意义
如果调用代码知道类的超类型,则可以给该方法一些意义。可以将其作为参数传递,从而使强制转换成为可能且有意义的。
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
}

请查看 Class.cast(Object) 方法。 - meriton
@Meriton 如果选择报告不可能转换的错误是抛出ClassCastException,则为True。例如,此代码返回null,这是另一种选择...必须做出选择...;-) - KLE

2

我认为这是因为 Class.forName(..) 没有为 T 参数化。当你触发 Eclipse 的自动补全时,它会假设 clazz.newInstance() 返回 Object。所以要保留强制转换并添加 @SuppressWarnings 注解。如果你没有正确使用该方法(例如 String str = Utils.create("java.util.ArrayList");),那么就会出现 ClassCastException,但这是正常的。


创建不会抛出ClassCastException。调用它的代码可能会,但不一定会立即发生。 - meriton
嗯,是的,但这取决于他如何修复他的方法,因为现在它们无法编译 - 他可以重新抛出异常,或返回null(这将导致NPE)。 - Bozho

1

你不能限制类型参数只包含名为className的类型。因此,调用者可以向您的create(String)函数提供任何类型,这当然不是类型安全的。

由于您无法静态地强制执行返回的实例实现任何接口(除非调用者通过传递相应的Class对象告诉您),我建议放弃使用泛型,并让调用者将其转换为所期望的任何类型。

public static Object create(final String className) {
    try {
        final Class<?> clazz = Class.forName(className);
        return create(clazz); 
    }
    catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

调用者可以这样写:

Foo foo = (Foo) create("my.cool.FooBar");

与之相反

Foo foo = create("my.cool.FooBar", Foo.class);

0
即使您可以让上面的代码工作,构造函数的newInstance()方法假定有一个零参数构造函数。
如果该类没有一个零参数构造函数,即您尝试创建的类的零参数构造函数已被明确声明为私有或包级别(取决于从哪个方法调用),则会出现IllegalAccessException。如果您让它工作,这是要添加到您的前提条件中的一些内容。

-1
我发现了一件有趣的事情:Type如果不是泛型类型,可以直接转换为Class。但是我仍然无法使用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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接