类的加载方式
类的加载方式
- 隐式加载
- 显式加载
隐式加载有几种情况:
- 首次通过 new 创建一个类的实例
- 首次调用类的静态成员
- 首次加载一个类,会先加载它的父类
- JVM 启动时,会自动加载定义了 main 方法的类
显式加载有两种方式:
- 通过 ClassLoader.loadClass
- 通过 Class.forName
区别
隐式加载是在首次使用时才加载类,显式加载是先提前加载类再使用。
显式加载用于实现反射特性,其主要步骤为:
- 加载 *.class 字节码文件获取 Class 对象
- 通过 Class 对象获取构造器
- 调用 newInstance 方法创建对象实例
import java.lang.reflect.Constructor;
public class Main {
public static void main(String[] args) {
try {
// 1. 加载 *.class 字节码文件获取 Class 对象
Class<?> c = Class.forName("A");
// Class<?> c = ClassLoader.getSystemClassLoader().loadClass("A");
// 2. 通过 Class 对象获取构造器
Constructor<?> cons = c.getConstructor();
// 3. 调用 newInstance 方法创建对象实例
A a = (A) cons.newInstance();
a.hello();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class A {
public A() {
System.out.println("调用无参构造函数");
}
public void hello() {
System.out.println("hello");
}
}
ClassLoader.loadClass 和 Class.forName 加载类的区别
类的装载包括 3 个步骤:加载(loading),链接(link),初始化(initialize)。
Class.forName
forName0 第二个参数 initialize 决定是否初始化,这里参数是 true 说明是准备初始化。因此,静态代码快和静态变量是会被执行和初始化的。
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
/** Called after security check for system loader access checks have been made. */
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
注意: Class.forName 实际上也是借助了 ClassLoader 加载类的。
ClassLoader.loadClass
第二参数 resolve,注释中说明这个参数代表是否链接,即类加载过程中的链接过程。这里,ClassLoader.loadClass 在加载类的过程中只走了第一步。
@CallerSensitive
public static ClassLoader getSystemClassLoader() {
// ...
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// ...
}
总结
- Class.forName 加载的类已经初始化了。
- ClassLoader.loadeClass 加载的类只是加载了,还没有进行链接。