java中cinit与init的执行顺序

java中cinit和init方法详解

文章目录

  • java中cinit和init方法详解
    • cinit方法
      • 总结
    • init方法
      • 总结

cinit方法

在类加载的准备阶段,虚拟机会为static的类变量赋上类型的初始值、常量附上定义的值(值必须为字面量或常量)。

public class Cinit {
    public static void main(String args[]) throws ClassNotFoundException {
        Class.forName(Father.class.getName());
         /*输出的是:
          * 准备阶段结束后
		  * 常量a=1
          * 变量b=0
          * 对象c=null
         */
    }
}
class Father{
    static{
        System.out.println("准备阶段结束后");
        System.out.println("常量a="+Father.a);
        System.out.println("变量b="+Father.b);
        System.out.println("对象c="+Father.c);
    }
    static final int a=1;
    static int b=2;
    static Object c=new Object();
}

在类加载的初始化阶段会执行cinit方法来为类变量赋上定义的值并执行类中的静态代码块。

public class Cinit {
    public static void main(String args[]) throws ClassNotFoundException {、
        Class.forName(Father.class.getName());                              
        /*输出的是:
         * 类型初始值:0
		 * 定义的值:1
        */
    }
}
class Father{
    static{
        System.out.println("类型初始值:"+Father.a);
    }
    static int a=1;
    static{
        System.out.println("定义的值:"+Father.a);
    }
}

cinit方法是由编译器按照从上到下的顺序收集的类中声明的静态变量和静态代码块组成的方法。

public class Cinit {
    public static void main(String args[]) throws ClassNotFoundException {
        Class.forName(Father.class.getName());
        /*输出的是:
         * 第一个静态代码块,a=0
		 * 第二个静态代码块,a=1
        */
    }
}
class Father{
    static{
        System.out.println("第一个静态代码块,a="+Father.a);
    }
    static int a=1;
    static{
        System.out.println("第二个静态代码块,a="+Father.a);
    }
}

虚拟机会先执行父类的cinit方法,之后再执行子类的cinit方法。

public class CinitTest {
    public static void main(String args[]) throws ClassNotFoundException {
        Class.forName(Son.class.getName());
        /*输出的是:
         * this is father's cinit
         * this is son's cinit
        */
    }
}
class Father{
    static{
        System.out.println("this is father's cinit");
    }
}
class Son extends Father{
    static int son=1;
    static{
        System.out.println("this is son's cinit");
    }
}

执行cinit过程中如果需要访问其他类(包括子类)的内容会去执行这个类的cinit后再继续执行

加载子类就会按照父类cinit->子类cinit的顺序执行(加载子类的时候先把父类与子类的变量分配之后再去执行父类与子类的cinit,因此父类cinit执行过程中访问子类的类变量无需插入子类的加载过程)

public class Cinit {
    public static void main(String args[]) throws ClassNotFoundException {
        System.out.println("加载father类");
        Class.forName(Father.class.getName());
        /*输出的是:
         * 加载father类
         * father's cinit
		 * father's cinit father=0
         * son's cinit
         * son's cinit father=0
         * son's cinit son=0
         * father's cinit son=1
        */
    }
}
class Father{
    static{
        System.out.println("father's cinit");
        System.out.println("father's cinit father="+Father.father);
        System.out.println("father's cinit son="+Son.son);
    }
    static int father=1;
}
class Son extends Father{
    static{
        System.out.println("son's cinit");
        System.out.println("son's cinit father="+Father.father);
        System.out.println("son's cinit son="+Son.son);
    }
    static int son=1;
}

加载子接口时不会执行父接口的cinit方法

public class Cinit {
    public static void main(String args[]) throws ClassNotFoundException {
        Class.forName(Son.class.getName());
        System.out.println(Father.father);
        //输出:1
        //如果是class则输出:0
        //因此可以判断加载子接口时不会执行父接口的cinit方法
    }
}
interface Father{  
    static int father=Son.son2;
    //如果加载子接口时也调用父接口的cinit
    //且父接口ciinit先于子接口调用,则father应该为son2的准备阶段的值也就是0
}
interface Son extends Father{
    static int son=1;//准备阶段为1
    static int son2=son;//准备阶段为0,cinit中赋值为son也就是1
}

forName(String name, boolean initialize, ClassLoader loader)的第二个参数可以控制是否执行初始化阶段

public class Cinit {
    public static void main(String args[]) throws ClassNotFoundException {
        Class.forName(Father.class.getName(), false, ClassLoader.getSystemClassLoader());
        System.out.println("不初始化的加载执行后");
        System.out.println("显式调用或创建father前");
        new Father();
        System.out.println("显式调用或创建father后");
        /*输出的是:
         * 不初始化的加载执行后
         * 显式调用或创建father前
         * this is father's cinit
         * this is Father() method
         * 显式调用或创建father后
        */
    }
}
class Father{
    static{
        System.out.println("this is father's cinit");
    }
    Father(){
        System.out.println("this is Father() method");
    }
}

总结

  • 在类加载的准备阶段,虚拟机会为static的类变量赋上类型的初始值、常量附上定义的值。
  • 在类加载的初始化阶段会执行cinit方法来为类变量赋上定义的值并执行类中的静态代码块。
  • cinit方法是由编译器按照从上到下的顺序收集的类中声明的静态变量和静态代码块组成的方法。
  • 虚拟机会先执行父类的cinit方法,之后再执行子类的cinit方法。
  • 执行cinit过程中如果需要访问其他类(包括子类)的内容会去执行这个类的cinit后再继续执行(加载子类而父类的cinit方法中访问子类的变量时除外)。
  • 加载子接口时不会执行父接口的cinit方法
  • 在多线程场景下,由JVM保证只有一个线程能执行该类的cinit方法。在执行过程中,其他线程全部阻塞等待。

init方法

在new对象之后,init方法之前,虚拟机会为实例变量赋上类型初始值,常量附上定义的值(值必须为字面量或常量)。

public class Init {
    public static void main(String args[]) throws ClassNotFoundException {
        new Father();
        /*输出的是:
         * init赋值之前
         * 常量a=1
         * 常量b=0
         * 变量c=0
         * init赋值结束后
         * 常量a=1
         * 常量b=1
         * 变量c=1
        */
    }
}
class Father{  
    {
        System.out.println("init赋值之前");
        System.out.println("常量a="+this.a);
        System.out.println("常量b="+this.b);
        System.out.println("变量c="+this.c);
    }
    final int a=1;
    final int b=this.a;
    int c=1;
    {
        System.out.println("init赋值结束后");
        System.out.println("常量a="+this.a);
        System.out.println("常量b="+this.b);
        System.out.println("变量c="+this.c);
    }
    int father=1;
}

init方法以调用父类的init方法作为开始,以调用自身的构造方法作为结束。

执行顺序为:父类init->自身的代码块与变量赋值->自身的构造方法

public class Init {
    public static void main(String args[]) throws ClassNotFoundException {
        new Son();
        /*输出的是:
         * Father类代码块 father=0
         * Father类构造方法 father=1
         * Son类代码块 father=1 son=0
         * Son类构造方法 father=1 son=2
        */
    }
}
class Father{  
    {
        System.out.println("Father类代码块"+" father="+this.father);
    }
    int father=1;
    Father(){
        System.out.println("Father类构造方法"+" father="+this.father);
    }
}
class Son extends Father{
    {
        System.out.println("Son类代码块"+" father="+this.father+" son="+this.son);
    }
    int son=2;
    Son(){
        System.out.println("Son类构造方法"+" father="+this.father+" son="+this.son);
    }
}

init方法中的代码块与变量赋值是由编译器按照从上到下的顺序收集的。

public class Init {
    public static void main(String args[]) throws ClassNotFoundException {
        new Father();
        /*输出的是:
         * Father类代码块 father=0
		 * Father类代码块 father=1
        */
    }
}
class Father{  
    {
        System.out.println("Father类代码块"+" father="+this.father);
    }
    int father=1;
    {
        System.out.println("Father类代码块"+" father="+this.father);
    }
}

init方法中出现生成新子类的地方会出现栈溢出的情况,cinit方法不会因为cinit只运行一次。

public class Init {
    public static void main(String args[]) throws ClassNotFoundException {
        new Father();
        /*
         *出现栈溢出异常
        */
    }
}
class Father{  
    {
        System.out.println("Father类init");
        new Son();
    }
}
class Son extends Father{
    {
        System.out.println("Son类init");
    }
    
}

总结

  • 在new对象之后,init方法之前,虚拟机会为实例变量赋上类型初始值,常量附上定义的值(值必须为字面量或常量)。
  • init方法以调用父类的init方法作为开始,以调用自身的构造方法作为结束。
  • init方法中的代码块与变量赋值是由编译器按照从上到下的顺序收集的。
  • init方法中出现生成新子类的地方会出现栈溢出的情况。

你可能感兴趣的:(JVM,jvm,java)