使用ClassLoader和Class.forName加载类之间的区别
以下是2个代码段
第一个使用ClassLoader类来加载指定的类
ClassLoader cls = ClassLoader.getSystemClassLoader(); Class someClass = cls.loadClass("TargetClass");
第二个使用Class.forName()来加载指定的类
Class cls = Class.forName("TargetClass");
上述方法之间有什么区别。 哪一个用于哪个目的?
快速回答(无代码示例)
使用显式的ClassLoader cls = ;
方法您可以灵活地从不是默认ClassLoader的ClassLoader加载类。 在您的情况下,您使用的是默认的System ClassLoader,因此它提供了与Class.forName(String name)
调用类似的整体结果(最终对象差异的实例化Class.forName(String name)
,但您可以引用另一个ClassLoader。
也就是说,只要知道ClassLoader是什么,你也可以使用Class.forName(String name, boolean initialize, ClassLoader loader)
。
例如,您的基于EAR的应用程序有自己的ClassLoader,其中包含一个XML Parsing库的版本。 您的代码通常使用这些类,但在一个实例中,您需要从库的早期版本中获取反序列化类(Application Server恰好在其整个ClassLoader中保存)。 因此,您可以引用该Application Server ClassLoader。
不幸的是,直到我们得到项目Jigsaw(JDK 8),这比我们想要的更常用:-)
其他答案非常完整,因为它们探索了Class.forName(...)
其他重载,并讨论了使用不同ClassLoader的可能性。
但是他们没有回答你的直接问题:“上述方法之间有什么区别?”,它处理Class.forName(...)
一个特定重载。 他们错过了一个非常重要的区别。 类初始化 。
考虑以下课程:
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"); } }
第一个类Main1
在运行时会产生一个输出,例如
time = 1313614183558
然而,另一个根本不会产生任何输出。 这意味着A
类虽然已加载,但尚未初始化(即,它的
尚未被调用)。 实际上,您甚至可以在初始化之前通过reflection查询类的成员!
你为什么要关心?
有些类在初始化时执行某种重要的初始化或注册。
例如,JDBC指定由不同提供程序实现的接口。 要使用MySQL,通常使用Class.forName("com.mysql.jdbc.Driver");
。 也就是说,您加载并初始化类。 我从未见过那些代码,但显然该类的静态构造函数必须使用JDBC在某处注册类(或其他东西)。
如果你做了ClassLoader.getSystemClassLoader().loadClass("com.mysql.jdbc.Driver");
,你将无法使用JDBC,因为尚未初始化已加载的类(然后JDBC不知道要使用哪个实现,就像你没有加载类一样)。
所以,这就是你问的两种方法之间的区别。
在你的具体案例中:
ClassLoader cls = ClassLoader.getSystemClassLoader(); Class someClass = cls.loadClass("TargetClass");
上面的代码将使用系统类加载器加载TargetClass
ALWAYS 。
Class cls = Class.forName("TargetClass");
第二个代码片段将使用用于加载正在执行该行代码的类的类加载器加载(并初始化) TargetClass
。 如果该类加载了系统类加载器,则这两种方法是相同的(除了类初始化,如Bruno的优秀答案中所述)。
哪一个使用? 对于使用reflection加载和检查类,我建议使用特定的类加载器( ClassLoader.loadClass()
) – 它使您可以控制并有助于避免不同环境之间可能存在的模糊问题。
如果需要加载AND初始化,请使用Class.forName(String, true, ClassLoader)
。
如何找到合适的类加载器? 这取决于您的环境:
- 如果您正在运行命令行应用程序 ,则可以使用系统类加载器或加载应用程序类的类加载器(
Class.getClassLoader()
)。 - 如果您在托管环境 (JavaEE,servlet容器等)内运行,那么最好先检查当前线程上下文类加载器 ,然后再回到上一点给出的选项。
- 或者只是使用你自己的自定义类加载器 (如果你是那种东西)
通常, 最傻瓜式的测试是使用Spring的ClassUtils.forName()
(参见JavaDoc )。
更深入的解释:
Class.forName()
的最常见forms是采用单个String
参数的forms,它总是使用调用者的类加载器。 这是加载执行forName()
方法的代码的类加载器。 相比之下,ClassLoader.loadClass()
是一个实例方法,需要您选择一个特定的类加载器,它可能是也可能不是加载该调用代码的加载器。 如果选择加载类的特定加载器对您的设计很重要,则应使用ClassLoader.loadClass()
或Java 2 Platform,Standard Edition(J2SE)中添加的forName()
的三参数版本:Class.forName(String, boolean, ClassLoader)
。
来源: Class.forName()
和ClassLoader.loadClass()
之间有什么区别?
此外, SPR-2611在使用Class.forName(String, boolean, ClassLoader)
时突出显示了一个有趣的模糊角落案例。
正如Spring问题所示,使用ClassLoader.loadClass()是推荐的方法 (当您需要从特定的类加载器加载类时)。
ClassLoader.loadClass()
使用指定的类加载器(在您的情况下为系统类加载器),而Class.forName()
使用当前类的类加载器。
当您不关心特定的类加载器并且希望与静态引用的类具有相同的类加载行为时,可以使用Class.forName()
。
从API文档 :
调用此方法相当于:
Class.forName(className, true, currentLoader)
其中currentLoader表示当前类的定义类加载器。
因此,主要区别在于将使用哪个类加载器(它可能与系统类加载器相同或不同)。 重载方法还允许您指定要显式使用的类加载器。
ClassLoader.loadClass()总是加载系统类加载器,而Class.forName()则加载任何类。 让我们看看这个例子,
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()); } }
当你运行这个程序时,你会在ClassLoader.getSystemClassLoader().loadClass("com.A");
得到一个Exception.loadClass ClassLoader.getSystemClassLoader().loadClass("com.A");
输出可能是:
time = 1388864219803 java.lang.ClassNotFoundException: com.A
第二种方法使用ClassLoader
一个ClassLoader
public static Class> forName(String className) throws ClassNotFoundException { return forName0(className, true, ClassLoader.getCallerClassLoader());
这就是JavaDoc所说的:
forName(String name, boolean initialize, ClassLoader loader)
指定的类加载器用于加载类或接口。 如果参数加载器为null,则通过引导类加载器加载该类。
因此,第二个选项使用System ClassLoader(实际上,它在第一个选项中执行的操作)。
加载数组类型时也有区别。 我认为classloader.loadClass(clazz)
不能处理数组类型,但Class.forName(clazz,true,classloader)
可以。
但静态initilizer块只在我们使用class.forname(“…”)时执行;
我刚刚测试过。