java 类初始化探究

不面试不知道自己有多菜,遇到一个类初始化的面试题,写不出来只能亡羊补牢了

class Simple {
    private static Simple simple = new Simple();
    private ChildObject childObject = new ChildObject();

    public Simple() {
        System.out.println("Simple");
    }

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

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

public class ParentObject {
    public ParentObject() {
        System.out.println("父类构造函数");
    }
}

public class ChildObject extends ParentObject {
    public ChildObject() {
        System.out.println("子类构造函数");
    }

    static {
        System.out.println("子类静态代码块");
    }
}

题目是写出输出,你可以试着写一下,答案我就放在最后了

静态变量,静态块,普通代码块,构造函数等在类初始化的执行顺序如下:

父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类实例变量
父类代码块
父类构造函数
子类实例变量
子类代码块
子类构造函数

* 如果是第二次初始化则没有静态变量和静态代码块的初始化

 

 

上题中还存在一个难点就是类中定义了一个静态的自己的实例。

class Simple {
    private static Simple simple = new Simple();
    ...
}

对于初始化阶段,虚拟机严格规范了有且只有5种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):

  1. 当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
    • 当jvm执行new指令时会初始化类。即当程序创建一个类的实例对象。
    • 当jvm执行getstatic指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
    • 当jvm执行putstatic指令时会初始化类。即程序给类的静态变量赋值。
    • 当jvm执行invokestatic指令时会初始化类。即程序调用类的静态方法。
  2. 使用 java.lang.reflect 包的方法对类进行反射调用时如Class.forname("..."),newInstance()等等。 ,如果类没初始化,需要触发其初始化。
  3. 初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
  4. 当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
  5. MethodHandle和VarHandle可以看作是轻量级的反射调用机制,而要想使用这2个调用, 就必须先使用findStaticVarHandle来初始化要调用的类。

 

本题就涉及到第4条。执行过程中会直接按静态变量,静态代码块的顺序初始化Simple类的信息。初始化静态变量时就会创建一个Simple的实例。这一步就相当于第二次调用Simple初始化的过程。仅会按实例变量,代码块,构造函数这个顺序初始化对应的Simple实例。结束初始化实例操作之后会继续静态变量,静态代码块的初始化任务。完成之后执行main函数。

问题的答案为

子类静态代码块
父类构造函数
子类构造函数
Simple
static Simple
main Simple

 

 

补充

class PrintObject {
    public PrintObject() {
    }

    public PrintObject(String msg) {
        System.out.println(msg);
    }
}

class ParentObject {
    private static PrintObject parentObject_ = new PrintObject("父类静态变量");
    private PrintObject parentObject = new PrintObject("父类实例变量");
    static {
        new PrintObject("父类静态代码块");
    }
    {
        new PrintObject("父类代码块");
    }
    public ParentObject() {
        System.out.println("父类构造函数");
    }
    public ParentObject(String msg) {
        System.out.println("父类构造函数");
    }
}

class ChildObject extends ParentObject {
    private static PrintObject parentObject_ = new PrintObject("子类静态变量");
    private PrintObject parentObject = new PrintObject("子类实例变量");
    static {
        System.out.println("子类静态代码块");
    }
    {
        new PrintObject("子类代码块");
    }
    public ChildObject() {
        System.out.println("子类构造函数");
    }
}

class Simple {
    private static Simple simple = new Simple();
    private ChildObject childObject = new ChildObject();

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

    {
        System.out.println("block Simple");
    }

    public Simple() {
        System.out.println("Simple");
    }

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

}

输出

父类静态变量
父类静态代码块
子类静态变量
子类静态代码块
父类实例变量
父类代码块
父类构造函数
子类实例变量
子类代码块
子类构造函数
block Simple
Simple
static Simple
main Simple
父类实例变量
父类代码块
父类构造函数
子类实例变量
子类代码块
子类构造函数

.java文件被编译成.class文件后,普通代码块的内容被放进了构造函数里的开头,如打开ParentObject.class文件中的内容为

class ParentObject {
    private static PrintObject parentObject_ = new PrintObject("父类静态变量");
    private PrintObject parentObject = new PrintObject("父类实例变量");

    public ParentObject() {
        new PrintObject("父类代码块");
        System.out.println("父类构造函数");
    }

    public ParentObject(String msg) {
        new PrintObject("父类代码块");
        System.out.println("父类构造函数");
    }

    static {
        new PrintObject("父类静态代码块");
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章