JVM的类加载机制中,准备阶段和初始化阶段尤为重要,在程序设计中有时候起到至关重要的作用。因此今天来根据一个例子来讲解JVM中<init>和<cinit>的过程。
吊炸天的Java题目,请问输出结果是什么?
public class JavaTest { public static void main(String[] args){ f1(); } static JavaTest javaTest = new JavaTest(); static { System.out.println("1"); } { System.out.println("2"); } JavaTest(){ System.out.println("3"); System.out.println("a=" + a + ", b=" + b); } public static void f1(){ System.out.println("4"); } int a = 100; static int b = 200; }
先给出正确答案:
2 3 a=100, b=0 1 4
init和clinit区别
①init和clinit方法执行时机不同
②init和clinit方法执行目的不同
init是instance实例构造器,对非静态变量解析初始化,而clinit是class类构造器对静态变量,静态代码块进行初始化。
<cinit>
在准备阶段,变量已经赋过一次系统要求的初始值(注意:如果这个类变量是static final的,那么在准备阶段就会根据程序员的意愿完成初始化,而非系统要求的初始值),而在初始化阶段,则根据程序员通过程序制定的主观计划去初始化类变量和其他资源,或者可以从另外一个角度来表达:初始化阶段是执行类构造器<clinit>()方法的过程。
编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。
虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。 因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object。由于父类的<clinit>()方法先执行,也就意味着父类中定义的静态语句块要优先于子类的变量赋值操作。
接口中不能使用静态语句块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。 但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法。 只有当父接口中定义的变量使用时,父接口才会初始化。 另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法,只有使用了接口的类变量时才会执行接口的<cinit>。
类的初始化顺序:总结:包含父子类和接口类
普通类:
静态变量
静态代码块
普通变量
普通代码块
构造函数
继承的子类:
父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类普通变量
父类普通代码块
父类构造函数
子类普通变量
子类普通代码块
子类构造函数
抽象的实现子类: 接口 - 抽象类 - 实现类
接口静态变量
抽象类静态变量
抽象类静态代码块
实现类静态变量
实现类静态代码块
抽象类普通变量
抽象类普通代码块
抽象类构造函数
实现类普通变量
实现类普通代码块
实现类构造函数
解题:
JavaTest程序的入口是public static void main,那么在调用这个main函数之前,需要执行类的加载过程,类加载成功后才会去调用main方法。
那么,
第一步:在类加载过程的准备阶段,先对b进行系统的赋值,b = 0。
第二步:在类加载过程的初始化阶段,执行<cinit>方法,那么先执行类变量的初始化,即:static JavaTest javaTest = new JavaTest();
第三步:在第二步的时候,<cinit>正在执行,而且此时进行类对象的初始化,会去调用<init>方法,因此会首先执行非静态变量的初始化:a = 100,然后执行非静态代码块:System.out.println("2"), 然后执行构造函数:System.out.println("3");System.out.println("a=" + a + ", b=" + b);此时a的值为100,b的值还是0,因为<cinit>还只执行到static JavaTest javaTest = new JavaTest();
第四步:<init>方法已经执行完了,那么就接下来执行<cinit>剩余的部分,先执行类的静态变量初始化:static int b = 200,再执行类的静态代码块:System.out.println("2")。此时<cinit>方法就执行完成了。
第五步:<cinit>和<int>方法都已经执行完成了,类已经加载完成,此时就是函数的调用了,JavaTest的函数入口是main()方法,因此会调用静态方法f1():System.out.println("4"); 到此,整个程序就执行完成了。
综上分析,可以得出结果:
2 3 a=100, b=0 1 4
来源:51CTO
作者:givemefive_001
链接:https://blog.csdn.net/u012588160/article/details/100108895