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

Java 使用ClassLoader和class.forName加载类之间的区别,java,reflection,classloader,Java,Reflection,Classloader,下面是两段代码片段 第一个使用ClassLoader类加载指定的类 ClassLoader cls=ClassLoader.getSystemClassLoader(); 类someClass=cls.loadClass(“目标类”) 第二个使用Class.forName()加载指定的类 Class cls=Class.forName(“TargetClass”) 上述方法之间的区别是什么。哪个用于哪个目的?快速回答(无代码示例) 使用显式的ClassLoader cls=方法您可以灵活地从不是

下面是两段代码片段

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

ClassLoader cls=ClassLoader.getSystemClassLoader();
类someClass=cls.loadClass(“目标类”)

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

Class cls=Class.forName(“TargetClass”)

上述方法之间的区别是什么。哪个用于哪个目的?

快速回答(无代码示例)

使用显式的
ClassLoader cls=方法您可以灵活地从不是默认类加载器的类加载器加载类。在您的例子中,您使用的是默认的系统类加载器,因此它给出了与
Class.forName(String name)
调用类似的总体结果(带有最终对象差异的实例化),但您可以引用另一个类加载器

也就是说,只要知道类加载器是什么,就可以使用
Class.forName(字符串名、布尔初始化、类加载器)

例如,基于EAR的应用程序有自己的类加载器,其中包含一个XML解析库版本。您的代码通常使用这些类,但在一个实例中,您需要从库的早期版本中获取反序列化类(应用程序服务器恰好在其整个类加载器中持有该类)。因此,您可以引用该应用程序服务器类加载器


不幸的是,在我们得到Jigsaw项目(JDK 8)之前,这项功能的使用频率比我们想要的要高:-)

ClassLoader.loadClass()
使用指定的类加载器(在您的例子中是系统类加载器),而
Class.forName()
使用当前类的类加载器

Class.forName()
可以在您不关心特定的类加载器并且想要与静态引用类相同的类加载行为时使用。

在您的具体案例中:

ClassLoader cls = ClassLoader.getSystemClassLoader();
Class someClass = cls.loadClass("TargetClass");
上述代码将始终使用加载
TargetClass

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

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

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

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

  • 如果您运行的是命令行应用程序,则可以使用或加载应用程序类的类加载器(
    class.getClassLoader()
  • 如果您在托管环境(JavaEE、servlet容器等)中运行,那么最好先检查,然后返回到上一点中给出的选项
  • 或者只需使用您自己的自定义类加载器(如果您喜欢这种类型的东西)
一般来说,最简单且经过测试的方法是使用Spring中的
ClassUtils.forName()
(请参阅)

更深入的解释:


Class.forName()
的最常见形式,即接受单个
字符串
参数的形式,总是使用调用方的类加载器。这是执行
forName()
方法加载代码的类加载器。相比之下,
ClassLoader.loadClass()
是一个实例方法,需要您选择一个特定的类加载器,它可能是也可能不是加载调用代码的加载器。如果选择一个特定的加载程序来加载类对您的设计很重要,那么您应该使用
ClassLoader.loadClass()
或Java 2平台标准版(J2SE)中添加的
forName()
的三参数版本:
class.forName(字符串、布尔值、类加载程序)

资料来源:


另外,在使用
Class.forName(字符串、布尔值、类加载器)
时,突出显示了一个有趣的晦涩角落


正如在Spring的那期中所看到的,使用ClassLoader.loadClass()是推荐的方法(当您需要从特定的类装入器装入类时)。

第二种方法使用
类装入器装入类

 public static Class<?> forName(String className) 
                throws ClassNotFoundException {
        return forName0(className, true, ClassLoader.getCallerClassLoader());
指定的类加载器用于 加载类或接口。如果 参数加载器为null,类为 通过引导类加载 加载器

因此,第二个选项使用系统类加载器(本质上就是它在第一个选项中所做的)

调用此方法相当于:

  Class.forName(className, true, currentLoader)
其中currentLoader表示当前的定义类加载器 班级

因此,主要区别在于将使用哪个类加载器(它可能与系统类加载器相同,也可能不同)。
重载方法还允许您指定要显式使用的类加载器。

在加载数组类型时也有区别。我认为
classloader.loadClass(clazz)
不能处理数组类型,但是
Class.forName(clazz,true,classloader)
可以。

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

但是,他们没有回答您的直接问题:“上述方法之间的区别是什么?”,哪种交易
forName(String name, boolean initialize, ClassLoader loader)
  Class.forName(className, true, currentLoader)
public class A {
  static { System.out.println("time = " + System.currentTimeMillis()); }
}
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");
  }
}
time = 1313614183558
package com;
public class TimeA {
      public static void main (String args[]) {
            try {
                final Class c = Class.forName("com.A");
                ClassLoader.getSystemClassLoader().loadClass("com.A");
            }catch(ClassNotFoundException ex) {
                System.out.println(ex.toString());
            }
      }
}

class A {
      static {
          System.out.println("time = " + System.currentTimeMillis()); 
      }
}
time = 1388864219803
java.lang.ClassNotFoundException: com.A