分享一道关于类、实例加载和初始化顺序的基础面试题
一 题目
二话不说,直接上题
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
一句话就是: 由父及子,静态先行,构造后行。