使用ClassLoader和Class.forName加载类之间的区别

Ebb*_*ham 23 java reflection classloader

以下是2个代码段

第一个使用ClassLoader类来加载指定的类

ClassLoader cls = ClassLoader.getSystemClassLoader(); Class someClass = cls.loadClass("TargetClass");

第二个使用Class.forName()来加载指定的类

Class cls = Class.forName("TargetClass");

上述方法之间有什么区别.哪一个用于哪个目的?

Bru*_*eis 48

其他答案非常完整,因为他们探索了其他重载Class.forName(...),并讨论了使用不同ClassLoader的可能性.

然而,他们没有回答你的直接问题:"上述方法之间有什么区别?",它涉及一个特定的超载Class.forName(...).他们错过了一个非常重要的区别.类初始化.

考虑以下课程:

public class A {
  static { System.out.println("time = " + System.currentTimeMillis()); }
}
Run Code Online (Sandbox Code Playgroud)

现在考虑以下两种方法:

public class Main1 {
  public static void main(String... args) throws Throwable {
    final Class<?> c = Class.forName("A");
  }
}

public class Main2 {
  public static void main(String... args) throws Throwable {
    ClassLoader.getSystemClassLoader().loadClass("A");
  }
}
Run Code Online (Sandbox Code Playgroud)

第一个类,Main1当运行时,将产生一个输出,如

time = 1313614183558
Run Code Online (Sandbox Code Playgroud)

然而,另一个根本不会产生任何输出.这意味着该类A虽然已加载,但尚未初始化(即,它<clinit>尚未被调用).实际上,您甚至可以在初始化之前通过反射查询类的成员!

你为什么要关心?

有些类在初始化时执行某种重要的初始化或注册.

例如,JDBC指定由不同提供程序实现的接口.要使用MySQL,通常会这样做Class.forName("com.mysql.jdbc.Driver");.也就是说,您加载并初始化类.我从来没有见过那些代码,但显然该类的静态构造函数必须使用JDBC在某处注册类(或其他东西).

如果你这样做了ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");,你将无法使用JDBC,因为尚未初始化已加载的类(然后JDBC不知道要使用哪个实现,就像你没有加载类一样).

所以,这就是你问的两种方法之间的区别.


Mar*_*urg 14

快速回答(无代码示例)

使用显式ClassLoader cls = <a ClassLoader>;方法,您可以灵活地从不是默认ClassLoader的ClassLoader加载类.在你的情况下,你使用的是默认系统类加载器,所以它提供了类似的总体结果(与最终目标的差异的实例化)作为Class.forName(String name)呼叫,但你可以参考另一个类加载器来代替.

也就是说,Class.forName(String name, boolean initialize, ClassLoader loader)只要您知道ClassLoader是什么,您也可以使用它.

例如,您的基于EAR的应用程序有自己的ClassLoader,其中包含一个XML Parsing库的版本.您的代码通常使用这些类,但在一个实例中,您需要从库的早期版本中获取反序列化类(Application Server恰好在整个ClassLoader中保存).因此,您可以引用该Application Server ClassLoader.

不幸的是,直到我们得到项目Jigsaw(JDK 8),这比我们想要的更常用:-)

  • 不要忘记:从不同类加载器加载的类实例化的对象不是`Equal()`,即使它们的数据是相同的.即使它们是从同一个类创建的(由两个不同的类加载器加载).这可能是难以发现的难以找到的错误的根源. (11认同)
  • 实际上,@ TMN是错误的.让`cl1`和`cl2`成为两个不同的类加载器,让`A`成为一个用`returne;'重写`.equals(...)`的类(也就是说,它总是返回true).然后`cl1.loadClass("A").newInstance().equals(cl2.loadClass("B").newInstance())`返回true. (3认同)

Nee*_*aks 12

在你的具体案例中:

ClassLoader cls = ClassLoader.getSystemClassLoader();
Class someClass = cls.loadClass("TargetClass");
Run Code Online (Sandbox Code Playgroud)

上面的代码将使用系统类加载器加载TargetClass ALWAYS.

Class cls = Class.forName("TargetClass");
Run Code Online (Sandbox Code Playgroud)

第二代码段将加载(并初始化)TargetClass的是使用加载正在执行该代码行该类的类加载器.如果该类加载了系统类加载器,则这两种方法是相同的(除了类初始化,如Bruno的优秀答案中所述).

哪一个使用?对于使用反射加载和检查类,我建议使用特定的类加载器(ClassLoader.loadClass()) - 它使您可以控制并有助于避免不同环境之间可能存在的模糊问题.

如果您需要加载AND初始化,请使用Class.forName(String, true, ClassLoader).

如何找到合适的类加载器?这取决于您的环境:

  • 如果您正在运行命令行应用程序,则可以使用系统类加载器或加载应用程序类的类加载器(Class.getClassLoader()).
  • 如果您在托管环境(JavaEE,servlet容器等)内运行,那么最好先检查当前线程上下文类加载器,然后再回到前一点给出的选项.
  • 或者只是使用你自己的自定义类加载器(如果你是那种东西)

一般来说,最简单和经过测试的将是使用ClassUtils.forName()Spring(参见JavaDoc).

更深入的解释:


最常见的形式Class.forName(),即采用单个String参数的形式,总是使用调用者的类加载器.这是加载执行该forName()方法的代码的类加载器.相比之下,ClassLoader.loadClass()是一个实例方法,需要您选择一个特定的类加载器,它可能是也可能不是加载该调用代码的加载器.如果选择一个特定的加载器来加载类对您的设计很重要,那么您应该使用Java 2 Platform,Standard Edition(J2SE)ClassLoader.loadClass()forName()添加的三参数版本:.Class.forName(String, boolean, ClassLoader)

来源:之间有什么区别?Class.forName()ClassLoader.loadClass()


此外,SPR-2611在使用时突出了一个有趣的模糊角落案例Class.forName(String, boolean, ClassLoader).

正如Spring问题所示,使用ClassLoader.loadClass()是推荐的方法(当您需要从特定的类加载器加载类时).