当我们谈论Java构造器时,我们在讨论什么?

构造器是用来做什么的?当然是用来构造对象的!但是构造对象的过程你了解吗?

构造对象过程中的坑

请阅读以下代码,并模拟其输出的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Father {
private int field = 200;

public Father() {
System.out.println("Father constructor before draw");
draw();
System.out.println("Father constructor after draw");
}

public static void main(String[] args) {
Father c = new Father();
c.draw();
}

void draw() {
System.out.println("Father draw:" + field);
}
}
1
2
3
4
5
Output:~
Father constructor before draw
Father draw:200
Father constructor after draw
Father draw:200

程序很正常的输出了两次200,然而当我们用一个子类去继承时:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Child extends Father {
private int value = 100;

public static void main(String[] args) {
Child c = new Child();
c.draw();
}

@Override
void draw() {
System.out.println("Child draw:" + value);
}
}

结果让人意外!

1
2
3
4
5
Output:~
Father constructor before draw
Child draw:0
Father constructor after draw
Child draw:100

这里涉及到继承和多态,当我们在构造对象的时候,消息的顺序一般是从自身向父类发送,逐层压入栈,然后顺序出栈构造(栈只是顺序,并不代表真正执行的结构)。

父Static域 -> 子Static域 -> 父构造器涉及的域 -> 父构造器 -> 子构造器涉及的域 -> 子构造器 ……

而如果构造对象时,父类执行了一些(可能会)被子类重写的方法,则会出现上述不可预知的错误。原因在于,当我们执行到“父构造器”时,此时调用了draw()方法,由于我们对象时Child类型的,编译器则会根据多态(运行时绑定)寻找到被子类重写(Override)的方法,但是此时还未初始化子类的value域,故而输出错误的值。

好在此时编译器为我们赋予了类私有域一个默认值(0、null),否则我们的程序就要空指针了!

所以,在构造对象(构造器)时,尽量不要调用非final的方法(private方法是隐式的final方法),否则会出现不可预知的问题,导致对象构建不完全。

当然,构造器还有一些其他的特性,比如private修饰时,若无其他的构造器,则无法构建对象,自然也无法被继承。这里就不一一展开了,有兴趣的朋友请自行阅读《Thinking In Java》。

文章目录
  1. 1. 构造对象过程中的坑
|