Java中父类构造器调用时机与方式及链式构造执行疑问
this()与隐式super()的执行顺序探究 测试代码
public class ConstructorChaining{ public static void main(String args[]) { new CCC(); } } class CCA { public CCA() { this(""); System.out.println("Inside A()"); } public CCA(String a) { System.out.println("Inside A(String)"); } } class CCB extends CCA { public CCB() { this(""); System.out.println("Inside B()"); } public CCB(String a) { //this(2); System.out.println("Inside B(String)"); } public CCB(int a) { System.out.println("Inside B(int)"); } } class CCC extends CCB { public CCC() { this(""); System.out.println("Inside C()"); } public CCC(String a) { this(5); System.out.println("Inside C(String)"); } public CCC(int a) { System.out.println("Inside C(int)"); } }
输出结果
该代码的输出结果为:
Inside A(String) Inside A() Inside B(String) Inside B() Inside C(int) Inside C(String) Inside C()
我的观察结论
基于上述输出,我总结出以下规律:
- 如果一个类的所有构造器重载都未显式调用
super(),那么该类的所有链式构造器重载会先执行完毕,之后才会隐式调用父类的构造器。 - 父类也遵循相同的执行模式,即父类的默认构造器会在子类所有链式构造器调用完成后才被隐式触发。
我用如下流程图示来描述这个执行逻辑:
CO1() --this()--> CO2() --this()--> ... --this()--> COn 构造器重载(CO) of Class Cn | 隐式super()调用 | v COn() <--this()-- ... <--this()-- CO2() <--this()-- CO1 构造器重载(CO) of Class Cn-1(Cn的父类) | 隐式super()调用 | v : | 隐式super()调用 | v CO1() --this()--> CO2() --this()--> ... --this()--> COn 构造器重载(CO) of Class C1(C2的父类)
我的疑问
如果上述观察正确,我有以下疑问:
- 是谁决定了要在当前类所有链式构造器执行完毕后才隐式调用
super()? - JDK/JRE中是如何实现这个行为的?是编译器生成了包含最后调用
super()的字节码,还是运行时动态决定调用时机?
解答
首先明确:这个行为是Java语言规范(JLS)强制要求的,并且完全是编译器在编译阶段处理完成的,和运行时没有关系。
1. 规则的制定者:Java语言规范(JLS)
Java的官方语言规范早就明确了构造器的执行规则:
- 任何构造器如果没有显式调用
this()(同一类的其他构造器)或者super()(父类构造器),编译器会自动在构造器的最开头插入隐式的super()调用(调用父类的无参构造器)。 - 但如果构造器显式调用了
this(),那么这个this()必须是构造器的第一条语句,此时编译器不会插入super()——直到链式调用的最后一个构造器(也就是没有调用this()的那个构造器),编译器才会在它的开头插入隐式的super()。
这就是你看到子类链式构造器先“全部执行完”的原因:本质上是每个构造器的this()都优先于任何其他代码执行,直到链的末端才触发父类的构造器调用。
2. 实现方式:编译期生成字节码
我们可以通过查看编译后的字节码直接验证这一点。比如拿CCC(int a)构造器来说,它没有调用this(),所以编译器会在它的字节码最开头插入super()(调用CCB的无参构造器)。而CCC(String a)调用了this(5),所以它的字节码里第一条指令就是调用this(int),不会有super()。
再看CCB(String a)构造器,它没有调用this()或super(),所以编译器会在它开头插入super()(调用CCA的无参构造器)——而CCA的无参构造器又调用了this(""),所以就形成了你看到的完整调用链:CCC() → CCC(String) → CCC(int) → CCB() → CCB(String) → CCA() → CCA(String)
然后从CCA(String)开始执行输出,再依次回溯执行每个构造器中this()之后的代码,这就是你看到输出顺序的核心原因。
简单总结编译器的处理逻辑:
- 每个构造器要么以
this()开头,要么以隐式/显式的super()开头。 - 链式调用的最终末端构造器,一定会触发父类的构造器调用,以此递归直到
Object类的构造器。
补充修正你的观察细节
你描述的“子类所有链式构造器重载执行完毕后才调用父类”其实更准确的说法是:子类构造器的链式调用会先走到链的末端,然后从末端构造器触发父类的构造器链式调用,父类同样走完自己的构造器链,再依次返回执行每个构造器中this()之后的代码。这也是为什么你的输出是从最顶层父类的末端构造器输出开始,然后依次向下回溯的原因。
内容的提问来源于stack exchange,提问作者MsA

