在 Java 中,对象的初始化顺序都是遵循一定的规则的。这涉及到类的构造函数调用、字段初始化以及继承关系中的初始化顺序。
当涉及到继承时,初始化顺序如下:
- 父类静态变量和静态初始化块:按照声明的顺序执行。
- 子类静态变量和静态初始化块:同样按照声明的顺序执行。
- 父类实例变量和实例初始化块:按照声明的顺序执行。
- 父类构造函数:执行父类的构造函数。
- 子类实例变量和实例初始化块:按照声明的顺序执行。
- 子类构造函数:执行子类的构造函数。
注意:在子类的构造函数中,可以通过 super()
显式地调用父类的构造函数(如果没有显式调用,则会自动调用父类的无参构造函数)。这个 super()
的调用必须在子类构造函数的第一行。
关于你提到的“父类的构造函数早于子类的属性初始化”,实际上,这是符合上述规则的。在子类的构造函数被调用之前,父类的构造函数已经执行完毕。这意味着父类中的所有实例变量和实例初始化块都已经被初始化。然后,子类的实例变量和实例初始化块才会被初始化,最后执行子类的构造函数。
这里有一个简单的例子来说明这个过程:
class Parent { | |
int parentField; | |
public Parent() { | |
System.out.println("Parent Constructor"); | |
parentField = 10; | |
} | |
{ | |
System.out.println("Parent Instance Block"); | |
} | |
} | |
class Child extends Parent { | |
int childField; | |
public Child() { | |
super(); // 隐式调用父类构造函数 | |
System.out.println("Child Constructor"); | |
childField = 20; | |
} | |
{ | |
System.out.println("Child Instance Block"); | |
} | |
} | |
public class Main { | |
public static void main(String[] args) { | |
new Child(); | |
} | |
} |
输出:
Parent Instance Block | |
Parent Constructor | |
Child Instance Block | |
Child Constructor |
从输出中可以看出,父类的实例初始化块和构造函数在子类的任何初始化之前执行。然后,子类的实例初始化块和构造函数才会执行。