首页 > 技术文章 > java类的加载时机和对象new的顺序相关的问题总结

dyingstraw 2019-03-03 21:55 原文

一、什么情况下下必须对类进行初始化?

1.遇到new,getstatic, putstatic, invokestatic这四条字节指令的时候,如果类没有进行初始化,则需要触发其初始化。这四条字节指令的常见场景:使用new实例化对象,读取或设置静态字段(如果被dinal修饰、已在编译器把结果放入常量池的静态字段除外),以及调用一个类的静态方法。场景基本与上方字节指令对应。

2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果这个类没有初始化,则初始化

3.当初始化一个类的时候,如果发现其父类没有初始化,先初始化其父类,父类初始化同上。

4.当虚拟机启动的时候,用户需要指定要执行的主类(包含main函数的类)先初始化。

5.当使用jdk1.7的动态语言支持时,如果java.lang.invoke.MethodHandle实例最后结果时EREF_getstatic, EREF_putstatic, EREF_invokestatic的方法句柄,并且其对应的类没有初始化,则初始化之。

对于这2中会触发类的初始化场景,成为对一个类的主动引用,除此之外,所有引用类的方法都不会触发类的初始化。

 

几种被动引用举例:

1.引用父类的静态字段,不会导致子类的初始化。

public class ClassA {
    public static String a="sa";
    static {
        System.out.println("sA");
    }
}

public class ClassB extends ClassA {
    public static String b="sb";
    static {
        System.out.println("sB");
    }
}
// 测试
public class TestAB {
    public static void main(String[] args){
        System.out.println(ClassB.a);
    }
}

可知,最终输出结果应该是:由此可见,子类对象并没有初始化。

sA
sa

2.当引用static变量修饰的final常量时,由于常量在编译的储器就已经被加入常量池,因此,不会触发子类和父类的初始化。

见代码:

public class ClassA {
    public static String a="sa";
    static {
        System.out.println("sA");
    }
}

public class ClassB extends ClassA {
    public static String final b="sb";
    static {
        System.out.println("sB");
    }
}
// 测试
public class TestAB {
    public static void main(String[] args){
        System.out.println(ClassB.b);
    }
}

输出结果:sb

4.但是是不是所有的被final修饰的常量都是这样子的结果?答案当然是No!,这个常量必须是编译期常量,下面有个例子?

public class ClassA {
    public static String a="sa";
    static {
        System.out.println("sA");
    }
}

public class ClassB extends ClassA {
    public static String final b= new String("sb");
    static {
        System.out.println("sB");
    }
}
// 测试
public class TestAB {
    public static void main(String[] args){
        System.out.println(ClassB.b);
    }
}

 输出结果:

sA

sB

sb

 由此可见,首先对ClassB初始化的时候,发现父类没被初始化,先初始化老爸,再儿子!

3.通过数组定义来引用类,不会触发类的初始化

public class ClassA {
    public static String a="sa";
    static {
        System.out.println("sA");
    }
}

public class ClassB extends ClassA {
    public static String b="sb";
    static {
        System.out.println("sB");
    }
}
// 测试
public class TestAB {
    public static void main(String[] args){
        // System.out.println(ClassB.b);
        ClassB[] classBS = new ClassB[100];
    }
}

输出结果为空,以为设ClassA,ClassB都没有被初始化!

 

接下来看几道相关的题:

题目1:

public class Main {

    public static void main(String[] args) {
        System.out.println("A");
        new Main();
        new Main();
    }

    public Main() {
        System.out.println("B");
    }

    {
        System.out.println("C");
    }

    static {
        System.out.println("D");
    }
}

以上程序输出的结果,正确的是?

答案:

DACBCB

解析:

  1. main所在的类最先初始化执行静态块代码:D
  2. main函数输出:A
  3. 实例化Main类,由于Main类已经被初始化。而静态代码块旨在类初始化的时候运行一次。同时构造代码块优先于构造函数执行:CB
  4. 同上输出:CB

 

参考文献:

周志明. 深入理解Java虚拟机[M]. 机械工业出版社, 2013.

 

 

 

 

推荐阅读