类加载、初始化的过程
因为之前对类加载这块不感兴趣,感觉实际应用中又用不到……但工作后发现并非如此,因此学习总结一下——类加载和初始化的过程让人很绕,有些搞不明白。
首先要明白,类的加载和初始化是两个过程,同样也是总是在一起的两个过程。(菜鸡如我一直都很糊涂)。
1、加载、初始化的含义
当我们第一次使用类时,就会触发类的加载、链接、初始化三个过程。(当然也有的说法会有其他过程,这里我们就简单一点,只说这三个过程)。
- 类加载,就是查找字节码,从字节码中创建一个class对象(由类加载器执行)
- 链接,就是验证类中的字节码,为静态域分配存储空间
- 初始化,就是为静态成员初始化及静态代码块执行(通俗来说就是赋值)的过程
当这三个对象都完成后,我们才能进行创建对象的操作,也就是new的过程,因为创建对象必须用到类的 Class 对象。
2、类加载、初始化的时机
时机就是第一次使用类时,一般包括三种情况:
- A的静态方法(如构造器)首次被访问时
- A的静态成员(如 i )首次被访问时
- 通过Class类,创建A的class对象
下面列出四种可能的情况:
public Class A extends B{
public static Integer i = 0;
public static Integer j = 0;
public double k = 0;
public double h = 0;
static{
print("A被初始化");
}
public static void main(String[] args) {
//1、new一个对象,这是第一次使用,故A被加载并初始化
A a = new A();
//2、这是第一次使用A的静态成员,故A被加载并初始化
print(A.i);
//3、这次有些特殊,这时候A被加载,但并未被初始化
Class aClass = A.class;
//4、这时候A被加载并被初始化
Class aClass = Class.forName("A");
}
}
3、类加载过程
加载过程,就是创建class对象的过程。
我们都知道类都有一个class对象,假如有一个类A,通过A.class就可以获取这个对象,而实际上class对象就是用来创建对象的。
加载过程,就是创建A的Class对象,为后面创建A的对象做准备。
上面的例子我们知道获取类A的Class对象的两种方法:
- 一种是常用的A.class
- 一种是Class的静态方法,Class.forName(“类名,需要包含包名”)
这两种方法有一定的区别,就是A.class只会加载类,不会自动的初始化类。
//3、这次有些特殊,这时候A被加载,但并未被初始化
Class aClass = A.class;
//4、这时候A被加载并被初始化
Class aClass = Class.forName("A");
4、类初始化过程
类的初始化过程大家就比较熟悉了,是在加载之后,一般都会进行(A.class这种就没有进行)。
总结一下,初始化顺序为:
- 先初始化父类(如果父类未被初始化)
- 按顺序初始化静态成员
- 静态代码块执行(与上一步优先级相同,按顺序来,但是一般书写在后面)
- 调用new,创建对象
- 初始化父类的非静态成员变量,调用父类的构造函数
- 按顺序初始化非静态成员
- 非静态代码块执行(与上一步优先级相同,按顺序来,但是一般书写在后面)
- 执行构造器
public Class A extends B{
public double k = 0;
public double h = 0;
public static Integer i = 0;
public static Integer j = 0;
//这块代码和静态变量一样,只执行一次
static{
i = 1;
}
//这块代码和非静态变量初始化时一起执行
{
k = 1;
}
A(){
print("A构造器执行");
}
}
如上面类A,它的初始化过程就是。
- 为 i 赋值0
- 为 j 赋值0
- 为 i 赋值1
- 为 k 赋值0
- 为 h 赋值0
- 为 k 赋值1
- 构造器执行,打印“A构造器执行”
5、特殊情况——静态常量
在类的初始化过程中,有一种特殊情况,就是static final修饰的常量。它不需要初始化,就可以直接访问,因为它是在编译期就准备好了。
注意,需要满足两个条件:
- static final修饰
- 常量
比如:
public static final Integer i = 0;
此处的 i 是不需要被初始化,就可以直接访问的。
但是下面:
public static final Integer j = ClassInitializarion.rand.nextInt(1000);
此处的 j 并不是常量,所以需要初始化之后才能访问,也就是说,访问 j 时,会触发类的初始化。
那上面的类A,用static修饰的成员,也是需要初始化吗?
public static Integer i = 0;
是的,对 i 访问时,i 需要先被链接,分配存储空间后,再执行初始化