loadClass(("fully qualified class name") 和 <ClassName>.class.getDeclaredConstructors 有什么区别

what is the difference between loadClass(("fully qualified class name") and <ClassName>.class.getDeclaredConstructors

class testMe{

    void show(){
        System.out.println("Hello");
    }

}

public class ClassloadersExample {
    public static void main(String args[]) {
        ClassLoader c = ClassloadersExample.class.getClassLoader(); //Line 1
        try {
            Class c1 = c.loadClass("test.testMe"); // Line 2
            Constructor a[] = c1.getDeclaredConstructors(); 
            for (Constructor constructor : a) {
                testMe m = (testMe)constructor.newInstance();
                m.show();
            }

            Constructor con[] = testMe.class.getDeclaredConstructors(); // Line 6
            for (Constructor constructor : con) {
                constructor.setAccessible(true);
                testMe t = (testMe)constructor.newInstance();
                t.show();
            }
        }
        catch(Exception e){
            System.out.println("exception");
        }

    }
}

我正在测试上面的代码。两者都给我相同的结果。我想了解第 1,2 行和第 6 行之间的区别。我可以通过这两种方法获得相同的结果。

回答

没有功能区别。正如您所发现的,有多种方法可以获取 Class 对象并实例化该 class 的实例。然而,它们都导致相同的结果。

一般来说,除非另有需要,否则始终:

  • 使用 class 文字或 getClass() 获得 Class
    • 示例 1:Class<Foo> cl = Foo.class;
    • 示例 2:Class<? extends Foo> cl = fooInstance.getClass();
  • 使用new关键字实例化实例
    • 示例:Foo f = new Foo();
    • 警告:有时 API 是使用 Builder Pattern, the Factory Method Pattern 等设计的...如果是这种情况,那么您将不得不使用这些方法。在内部,构建器和工厂方法甚至可能使用 new 关键字。

解释和次要(?)差异

在我脑海中,这些是我能想到的获得 Class 对象的方法:

  1. 使用class literals
    • Class<Foo> cl = Foo.class;
  2. 正在实例上调用 getClass()
    • Class<? extends Foo> cl = fooInstance.getClass();
  3. 呼叫Class.forName(String)
    • Class<?> cl = Class.forName("some.package.Foo");
    • 这是 shorthand Class.forName("some.package.Foo", true, currentClassLoader)
  4. 呼叫ClassLoader.loadClass(String)
    • Class<?> cl = classLoader.loadClass("some.package.Foo");
    • 不一定加载 Class。如果 Class 已经加载,那么加载的实例将被 returned.

以上都将得到代表some.package.FooClass对象。很可能(我不是 100% 确定),方法 1、2 和 3 最终都会委托给方法 4。

您会注意到 Class 对象(<> 部分)的通用签名因获取 Class 的方式而异。方法 1 和 2 知道 Class 在编译时是什么类型,return 和 Class 也可以使用适当的泛型。而方法 3 和 4 不知道 Class 在运行时将代表什么类型,因此 return 一个 Class<?>? 是一个通配符)。

关于方法 3 的一些注意事项。正如我上面提到的,Class.forName(String) 是 shorthand 对于 Class.forName(String, boolean, ClassLoader) 其中 boolean 将是 true 并且ClassLoader 将是当前的 ClassLoaderboolean参数决定是否初始化Class。初始化 class 意味着(除其他事项外?)初始化所有 static 变量和 运行 static 初始化器。因此,虽然方法 1、2 和 4 将 不会 初始化 Class,但方法 3 。如果您不希望方法 3 初始化 Class,则需要使用更长的版本并使 boolean 参数 false.

问题评论中的link说的是为什么要使用方法3或4。

创建实例

我又想到了这些实例化对象的方法:

  1. 使用new关键字
    • Foo f = new Foo();
  2. Using Class.newInstance()
    • Foo f = fooClass.newInstance();
    • 要求 class 有一个无参数的构造函数
    • 从 Java 9 开始弃用,支持使用 Constructor 对象
  3. 使用 Constructor 对象之一
    • Foo f = fooClass.getConstructor().newInstance();

这里的主要区别是如何每个方法创建一个实例。第一种方法仅使用 new 关键字。第二和第三种方法使用reflection。当您在编译时不知道类型时,反射很有用,但在需要时应避免使用。

方法三使用Class.getConstructor(Class<?>... paramterTypes)。由于我传递的是一个空的参数类型数组,因此 returned Constructor 是一个无参数构造函数。如果 class 没有无参数构造函数,这将失败。

您对 getDeclaredConstructors() 的使用只是 returns 所有 构造函数,然后您选择您想要的一个并调用 newInstance。我在 3 中给出的示例通过直接使用 public 无参数构造函数来绕过这个。使用 getDeclaredConstructors() 还将为您提供 非 public 构造函数(即 protectedpackageprivate) .这可以让您调用非 public 构造函数,否则您将无法调用。但这仅在您可以访问构造函数的情况下才可以;您需要获得任何已安装 SecurityManager 的许可,并且(如果使用 Java 9+)class 所在的包必须是反射可访问的 (opens)到你的模块。

一些链接