Java 反思:你如何获得你不知道的 class 的构造函数?

Java Reflection: how can you get constructor of a class you don't know?

假设您正在进行一般性反思class。您将以字符串格式获得 class 的名称: 所以你做 Class.forName(class_name) 得到 class 名字。

下一步是获取构造函数,假设没有任何参数,您可以执行 Class.forName(class_name).getConstructor(null)。

你现在如何获得这个实例?您将不得不沮丧,但是您沮丧什么? class_name name = (这里是什么) Class.forName(class_name).getConstructor(null).newInstance(null)

我试着把class_name放在括号里 class_name name = (class_name) Class.forName(class_name).getConstructor(null) .newInstance(null) 但出现错误。好吧,不编译。

更新: 唯一已知的是构造函数没有任何参数,并且实例没有参数。所以你可以将 null 传递给两者。

你要求的是不可能的。

如果您使用 Class.forName(someClassName) 加载 class,这意味着您在编译时 不知道 class 会是什么,您只会在运行时知道它(当定义 someClassName 的值时)。

这意味着,您不能基于该特定类型进行编码(因为如果您想在编译时输入代码,那么您应该在编译时知道确切的类型,而这不是您的情况)。

但是,即使您只能将您的实例声明为 Object obj = ... 而不是 SomeClassName obj = ...(据我所知,这是您想要做的),请放心,您的 obj 将在运行时实际上是 SomeClassName 的一个实例,即使编译器还不知道它。

如果您添加更多有关您尝试执行的操作的详细信息(我的意思是为什么您需要将反射实例转换为特定类型),那么我可以完成我的回答以解决您的确切问题。

编辑 回答评论中的问题:

so inherently Java knows that it's a Foo class but it'll just return a generic Object because it wants to be sure that it's correct?

没有。 编码时有两个阶段:compile-time 和 run-time。 compile-time 是代码被编译成可执行文件的时刻。 run-time 是代码执行的时刻。

取你问题的初始表达:

Object obj = Class.forName(class_name).getConstructor().newInstance();

变量class_name什么时候取具体值?好吧,那是在运行时(代码运行时)。

这意味着,编译器根本不可能知道这条指令的return是什么,直到代码执行时, 之前只能说是Object(因为在Java里面什么都是Object)。

但同样,在运行时,如果您传递 class Foo 的名称,实例将是 Foo(当然假设反射操作正常工作)。

the only time you cast is when you know what class your dealing with. But then what is the point of ever casting it?

没错,只有知道要投什么才可以投。 并且有一个关于铸造的确定点。

我们举一个很简单的例子。 假设您有一个 String 对象,例如 String str = "something that is a string";。 知道str是一个String,就可以调用属于classString的方法,例如:

str.split(" "); //<-- valid because str is a String
str.replace("o", "c"); //<-- valid because str is a String

现在,假设 str 是一个 String,但它被声明为 Object:

Object str = "something that is a string";

尽管 str 具体是 String,但您将无法再使用该代码:

str.split(" "); //<-- INVALID: the class Object doesn't have a method .split()
str.replace("o", "c"); //<-- INVALID: the class Object doesn't have a method .replace() 

因此,如果是这种情况,您可以将 str 转换为 String 并且您将能够做您正在做的事情:

Object str = "something that is a string";
String str2 = (String) str;
str2.split(...);
str2.replace(...);
etc.

当然,只有当 Object str 实际上是 String 时才有可能进行转换,如果它是(例如)Integer,那会引发 ClassCastException.

那你为什么要施法呢?因为在编译时,您需要针对特定​​对象编写代码,为此您需要强制转换以正确访问方法和属性。

ClassConstructor 提供一切 你需要完成你的目标。

  1. Class.forName(String) 你得到 Class 对象。
  2. 通过调用它的 getConstructors() 方法你得到一个数组 Constructor 个对象(代表 构造函数)。
    javadoc of Class.getConstructors().
  3. 然后通过调用其中任何一个的 getParameterTypes() 方法 Constructors 你得到一个 Class 对象数组 (构造函数形式参数的类型)。
    javadoc of Constructor.getParameterTypes().
  4. 现在你知道任何构造函数需要什么类型的参数了。 然后你可以实际调用带有这些参数的构造函数 通过调用 constructor.newInstance(param1, param2, ...).

示例:使用此代码

Class<?> clazz = Class.forName("java.util.HashMap");
System.out.println(clazz);
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
    System.out.println("  constructor: " + constructor);
    Class<?>[] parameterTypes = constructor.getParameterTypes();
    for (Class<?> parameterType : parameterTypes) {
        System.out.println("    parameterType: " + parameterType);
    }
}

你得到了java.util.HashMap的所有构造函数及其参数类型:

class java.util.HashMap
  constructor: public java.util.HashMap(int)
    parameterType: int
  constructor: public java.util.HashMap(int,float)
    parameterType: int
    parameterType: float
  constructor: public java.util.HashMap(java.util.Map)
    parameterType: interface java.util.Map
  constructor: public java.util.HashMap()