初始化
类的初始化是类加载的最后一个步骤,在准备阶段时类中的变量都为系统规定的初始值,在初始化阶段会根据程序来进行。
更直接的表达:初始化阶段就是执行类构造器< clinit >()方法过程。
< clinit >()方法的产生:
是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的。编译器收集顺序是由语句在源文件中出现的顺序决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。
public class Test{
static{
i = 0; //变量赋值可以正常编译通过
System.out.print(i);//编译器会提示 “非法向前引用”
}
static int i = 1;// 定义在静态语句快之后的变量
}
< clinit >() 方法与类的构造方法(< init >())不同,它不需要显示的调用父类的构造器,Java虚拟机保证在子类的< clinit >() 方法执行前,父类的< clinit >() 已经执行完毕。因此Java虚拟机中第一个执行的< clinit >() 方法的类型肯定是 java.lang.Object。
由于父类的< clinit >() 方法先执行,所以父类中定义的静态语句块要优于子类的变量赋值操作。
例如:
static class Parent{
public static int A = 1;
static{
A = 2;
}
}
static class Sub extends Parent{
public static int B = A;
}
public static void main(String[] args){
System.out.print(Sub.B);
}
输出的为2不为1
- < clinit >() 方法对于类和接口来说不是必须的,如果一个类没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成 < clinit >()方法。
- 接口中不能使用静态代码块,仍然有变量的赋值操作,接口执行< clinit >()方法不需要先执行父接口的< clinit >()方法,因为只有当父接口定义的变量被使用时才会对父接口初始化,接口的实现类在初始化时也一样不会执行接口的< clinit >()方法。
- Java虚拟机必须保证一个类< clinit >()方法在多线程环境中被正确地加锁同步,如果多个线程初始化一个类时,会造成其他线程进入阻塞队列,有可能造成进程阻塞。
注意:其他线程虽然会被阻塞,但如果执行< clinit >()方法的那条线程退出该方法后,其他线程唤醒后也不会在进入该方法了,同一个类加载器下,一个类型只会被初始化一次。