类初始化顺序

类的初始化遵循的顺序很多人都知道是这样:
(1)调用基类构造器,不断重复这个过程直到最底层
(2)再按照声明的顺序调用成员的初始化方法
(3)调用类构造器的主体

类初始化的实际过程

但是,考虑这样一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Animal{

public void walk(){
System.out.println("Animal.walk()");
}
Animal(){
System.out.println("Animal() before walk()");
walk();
System.out.println("Animal() after walk()");
}
}

class Cat extends Animal{
private int step = 100;

@Override
public void walk() {
System.out.println("Cat.walk(), step = " + step);
}

Cat(int step){
this.step = step;
System.out.println("Cat.Cat(), step = " + step);
}
}
public static void main(String[] args) {

new Cat(500);

}

Animal类的walk方法被设计为在Cat类中被重写,但是Animal的构造器会调用这个walk方法,这导致了对Cat.walk()的调用。
上述代码的生成结果是:

Animal() before walk()
Cat.walk(), step = 0
Animal() after walk()
Cat.Cat(), step = 500

我们会发现当Animal的构造器调用walk()方法的时候,step不是默认的初始值100,而是0。
因此前面讲的类初始化的顺序并不完整,实际上类的初始化顺序应该是这样的:
(1)在所有事情发生之前先分配空间,将分配给对象的存储空间初始化成二进制的零(或者某些特殊数据类型中与零等价的值)
(2)如前所述的调用基类构造器,这个时候在调用cat构造器之前就要调用walk方法,因此此时的step值是0
(3)按照声明的顺序调用成员的初始化方法
(4)调用类构造器的主体
当然,这种错误并不容易发现,因此推荐大家如果可以的话,构造器中尽可能避免调用其他方法,在构造器内唯一能够安全调用的那些方法是基类的final方法(因为他们不会被重写,也就不会有上述的问题)。