子父类初始化顺序
Java基础知识科普:
Java中的对象有两种类型:编译时类型和运行时类型。编译时类型指在声明对象时所采用的类型,运行时类型指为对象赋值所才采用的类型。
在如下代码中,person对象的编译时类型为Person,运行时类型为Student,因此无法在编译时获取在Student类中定义的方法:
Person person = new Student();
原题
背景:在头条上看到如下的一道题。请问输出的结果是什么?
public class Test {
static class Father {
public int money = 1;
public Father() {
money = 2;
showMoney();
}
public void showMoney() {
System.out.println("F money:" + money);
}
}
static class Son extends Father {
public int money = 3;
public Son() {
money = 4;
showMoney();
}
@Override
public void showMoney() {
System.out.println("S money:" + money);
}
}
public static void main(String[] args) {
Father son = new Son();
System.out.println("main money:" + son.money);
}
}
输出的结果:
S money:0
S money:4
main money:2
要想知道为什么会输出这个结果,需要熟练知道子父类初始化顺序。
分析
public class Test {
static class Father {
public int money = 1;
public Father() {
System.out.println("=======Father Construction Method Start========");
money = 2;
showMoney();
System.out.println("=======Father Construction Method End========\n");
}
public void showMoney() {
System.out.println("F money:" + money);
}
}
static class Son extends Father {
public int money = 3;
public Son() {
System.out.println("=======Son Construction Method Start========");
money = 4;
showMoney();
System.out.println("=======Son Construction Method End========\n");
}
@Override
public void showMoney() {
System.out.println("S money:" + money);
}
}
public static void main(String[] args) {
System.out.println("=======Main Method Start========");
Father son = new Son();
System.out.println("main money:" + son.money);
System.out.println("=======Main Method End========");
}
}
输出的结果如下。我们按输出内容进行解析:
Main Method Start
:表示进入了main方法体Father Construction Method Start
:当我们通过Son的构造方法创建Son对象时,需要先执行父类的构造方法。进入了父类的构造方法有此输出S money:0
:执行父类构造方法时,其中执行了showMoney方法;此方法被子类重写了,根据运行时类型,实际创建的对象是子类,所以调用此方法时会去调用子类中重写的showMoney方法;执行子类的showMoney方法时,子类的money属性还没有初始化(构造方法执行完毕才会初始化属性)Father Construction Method End
:当父类构造方法中showMoney方法执行完毕后,输出此行,表示父类构造方法执行完毕Son Construction Method Start
:通过子类的构造方法创建对象时,将父类构造方法执行完毕后,就进入了子类的构造方法S money:4
:在执行了money = 4;
后,son对象中的money属性的值就为4了,调用showMoney方法时使用的是重写的方法.Son Construction Method End
:执行完showMoney方法后,输出此行信息,表示子类的构造方法也执行完毕main money:2
:son对象指向的Father类,这是编译时声明,通过对象.属性
即son.money输出的就是son对象指向类中的属性。(如果指向Son类,此处输出的就是4)Main Method End
:main方法执行完毕
=======Main Method Start========
=======Father Construction Method Start========
S money:0
=======Father Construction Method End========
=======Son Construction Method Start========
S money:4
=======Son Construction Method End========
main money:2
=======Main Method End========
总结
这道题中考到了:
- 多态情况下,通过构造方法创建对象时,是先执行子类还是父类的构造方法?
- 创建子类对象时,父类构造方法中,调用了被子类重写的方法,是执行子类还是父类中的方法?
- 先执行构造方法还是先初始化类中的属性?
- 通过
对象.属性
来获取属性值时,获取到的是子类还是父类的属性值?
通过实践证明:
- 通过构造方法创建对象时,先判断初始化的类对象是否是有父类;有父类,则先执行父类的构造方法。
- 执行方法时,总是根据运行时类型来判断到底是执行父类还是子类中的方法
- 在一个类中,先初始化属性再执行构造方法。存在继承关系时,先执行父类构造方法,父类构造方法执行完毕后才会初始化子类中的属性。
- 通过
对象.属性
来获取属性值时,总是根据编译时类型来判断输出父类还是子类中的属性值
验证结论2
将main方法中改造如下:
public static void main(String[] args) {
System.out.println("=======Main Method Start========");
Father son = new Father();
System.out.println("main money:" + son.money);
System.out.println("=======Main Method End========\n");
}
输出结果:可以看到父类构造方法中调用showMoney方法时输出的是F money:2
,我们调用Father构造方法时,运行时类型是Father,执行showMoney也就是Father类中的方法了。
所以这条结论是正确的。
=======Main Method Start========
=======Father Construction Method Start========
F money:2
=======Father Construction Method End========
main money:2
=======Main Method End========
验证结论4
将main方法改造如下:
public static void main(String[] args) {
System.out.println("=======Main Method Start========");
Son son = new Son();
System.out.println("main money:" + son.money);
System.out.println("=======Main Method End========\n");
}
输出结果如下,和我们原题中输出结果的差距就是main money:4
,我们的改动就是将son对象指向了Son类。当Son类的构造方法执行完毕时,Son类中的money属性值为4,Father类中的money属性值为2,输出的是4。表示我们的结论是正确的。
=======Main Method Start========
=======Father Construction Method Start========
S money:0
=======Father Construction Method End========
=======Son Construction Method Start========
S money:4
=======Son Construction Method End========
main money:4
=======Main Method End========
综上所述
综上结论,我们只需要知道:父类的构造方法优先于子类构造方法创建,父类构造方法优先于子类属性加载,执行方法时使用的是运行时类型,对象.属性
使用的是编译时类型。