分享一道关于类、实例加载和初始化顺序的基础面试题

一 题目

  二话不说,直接上题

public class Son extends Father{

    private int i = test();
    private static int j = method();

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

    //构造方法
    Son(){
//        super();
        System.out.println("(7)");
    }

    //非静态代码块
    {
        System.out.println("(8)");
    }

    //非静态方法
    public int test(){
        System.out.println("(9)");
        return 0;
    }

    //静态方法
    public static int method(){
        System.out.println("(10)");
        return 0;
    }

    //main方法
    public static void main(String[] args) {
        Son son = new Son();
        System.out.println();
    }
}

class Father{
    private int i = test();
    private static int j = method();

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

    //构造器
    Father(){
        System.out.println("(2)");
    }

    //非静态代码块
    {
        System.out.println("(3)");
    }

    //public 非静态方法
    public int test() {
        System.out.println("(4)");
        return 0;
    }

    //public 静态方法
    public static int method() {
        System.out.println("(5)");
        return 0;
    }
}

 main方法执行后,输出顺序是什么? 答案是:

5 ===> 1 ===> 10 ===> 6 ===> 9 ===> 3 ===> 2 ===> 9 ===> 8 ===> 7
  
与你的答案是否一致呢?如果一致,说明你的这方面的知识过关了。

二 分析

  2.1 考点

      1.类的加载和初始化

    2.实例的创建和初始化

    3.方法的重写

  2.2 步骤分析

    步骤1:

      程序从main方法开始,首先会加载和初始化main方法所在的类Son,Son类的初始化,要执行类中的静态代码块、为静态成员变量赋值 (这两者按照代码从上到下的顺序执,谁在前面,谁先执行)。

      但是,当类Son被加载后,发现Son类还有一个父类-----Father类,这个时候会先对Father类进行加载和初始化,初始化Father类,需要执行静态代码块、为静态成员变量赋值。

        所以,执行顺序为:5 ===> 1 ===> 10 ===> 6 (先初始化父类,然后在初始化子类)

        这一点也可以验证,main方法中没有其他代码,只有一个main方法,也会出现这个执行顺序。

 

    步骤2:

      类加载和初始化完成后,main方法继续执行包含的代码,Son son = new Son();执行到这一步,会调用子类Son的无参构造方法,这里有一点需要注意:无论,
     子类有没有显示调用super();即调用父类的构造方法,实际上都会去调用父类的构造方法。
     也就是你无论写不写super(),都会去先调用父类的构造方法,上面的题目代码中,我已经标记了出来。
     所以,此时会先去调用父类的构造方法,创建父类的实例,并且初始化。
     实例的创建和初始化的过程会先进行非静态成员变量的赋值、执行非静态代码块(这两者也是按照代码从上到下的顺序,谁在前面,谁先被执行),最后执行构造方法里面的代码。
     整个过程就是先执行父类实例的初始化,然后执行子类实例的初始化。
     所以顺序是:
4 ===> 3 ===> 2 ===> 9 ===> 8 ===> 7 (先实例化父类,然后实例化子类)
     但是,这个顺序并不是程序运行结果的顺序。
所以接下来有步骤3


   步骤3:
    这个知识点是关于方法重写的,首先我们要知道,有三种情况下子类是不能对父类的方法进行重写的:
       1.父类方法被 private 默认包权限修饰 由于封装性的问题导致不能重写。
       2.父类方法被final修饰,final修饰的方法,不能被子类重写;
       3.父类的方法被static修饰,静态方法是不能被重写的。
     上面题目中涉及到了两个方法,一个public 非静态的 test(),另一个是 public static 静态方法method(); 而子类中也提供了一模一样的两个方法。
     根据上面介绍的重写规则,子类中的test()方法时重写方法,而method则不是重写。
     所以,返回到步骤2父类实例初始化的过程,首先会为非静态字段赋值,也就是会调用test()方法,问题就出现在这,这个test()方法是调用父类的呢,还是子类的呢?
     此时调用的是子类重写的方法.
     所以步骤2正确的顺序是:
9 ===> 3 ===> 2 ===> 9 ===> 8 ===> 7

三 总结

  所以,最后的执行顺序就是:

  5 ===> 1 ===> 10 ===> 6 ===> 9 ===> 3 ===> 2 ===> 9 ===> 8 ===> 7

  一句话就是: 由父及子,静态先行,构造后行。

原文地址:https://www.cnblogs.com/BoildWater/p/11377017.html