引例
public class A extends B {
public int a = 100;
public A() {
super();
System.out.println(a);
a = 200;
}
public static void main(String[] args) {
System.out.println(new A().a);
}
}
class B {
public B() {
System.out.println(((A) this).a);
}
}
例子代码很简单,不多做解释了,直接看输出:
0
100
200
对照这个输出,我们来详细分析一下对象的初始化顺序:
为A类分配内存空间,初始化所有成员变量为默认值,包括primitive类型(int=0,boolean=false,…)和Reference类型。
调用A类构造函数。
调用B类构造函数。
调用Object空构造函数。(java编译器会默认加此构造函数,且object构造函数是个空函数,所以立即返回)
初始化B类成员变量,因为B类没有成员变量,跳过。
执行sysout输出子类A的成员变量小a。// 此时为0
初始化A类成员变量,将A类成员变量小a赋值100。
执行sysout输出当前A类的成员变量小a。// 此时为100
赋值当前A类的成员变量小a为200。
main函数中执行sysout,输出A类实例的成员变量小a。// 此时为200
加粗的那两行描述是重点,结论是成员变量初始化是在父类构造函数调用完后,在此之前,成员变量的值均是默认值。 Dzone作者就是栽在这里,没有仔细分析成员变量初始化在对象初始化中的顺序,造成了程序未按原意执行。
其实这类问题,熟悉原理是一方面,本质上只要不在构造函数中插入过多的业务逻辑,出问题的概率也会低很多。
类加载简单时序图
程序的输出是什么?
public class Base {
private String baseName = "base";
public Base() {
callName();
}
public void callName() {
System.out.println(baseName);
}
static class Sub extends Base {
private String baseName = "sub";
@Override
public void callName() {
System.out.println(baseName);
}
}
public static void main(String[] args) {
Base b = new Sub();
}
}
有了前面的引例,这道题答案就比较简单了
答案: null
public class Demo {
class Super {
int flag = 1;
Super() {
test();
}
void test() {
System.out.println("Super.test() flag=" + flag);
}
}
class Sub extends Super {
// int flag;
Sub(int i) {
flag = i;
System.out.println("Sub.Sub()flag=" + flag);
}
@Override
void test() {
System.out.println("Sub.test()flag=" + flag);
}
}
public static void main(String[] args) {
new Demo().new Sub(5);
}
}
答案:
Sub.test()flag=1
Sub.Sub()flag=5
在我刚做这道题的时候也是有不少疑惑
为什么输出会是 1, 5 不应该是 0, 5
因为覆写了,没有指明 super 调用父类方法,父类的方法怎么都不可能被调用
所以自己打断点查找原因得到一个规律
我将构造函数的执行化为 三个阶段
class Super {
int flag = 1; // ②
Super() { // ①
test(); // ③
}
void test() {
System.out.println("Super.test() flag=" + flag);
}
}
通过这个规律,我将注释写在先前的代码上
public class Demo {
class Super {// ③ 除 Object 无继承,开始执行构造函数
int flag = 1; // ⑤ 加载成员变量完毕
Super() { // ④ 程序运行到这里的时候,开始加载成员变量
test(); // ⑥ 调用子类方法
}
void test() {
System.out.println("Super.test() flag=" + flag);
}
}
class Sub extends Super {// ② Sub 类有父类,继续寻找
Sub(int i) { // ⑧ 开始加载子类的构造函数
flag = i; // ⑨ 重新对父类变量 flag 赋值
System.out.println("Sub.Sub()flag=" + flag); // ⑩ 对父类变量进行输出
}
@Override
void test() {
System.out.println("Sub.test()flag=" + flag); // ⑦ 输出父类的变量 flag
}
}
public static void main(String[] args) {
new Demo().new Sub(5); // ① 开始寻找 Sub 类
}
}
PS: 这些都是简单题,还有静态变量,静态类,内部类,内部静态类,内部静态类静态变量 try catch finally return 各种混合...