弹性构造器(Flexible Constructor Bodies, JEP 513)#
版本信息
- 最低 JDK:25(23/24 预览 → 25 正式)
- JEP:JEP 513: Flexible Constructor Bodies
为什么需要#
JDK 8 规定 super(...) / this(...) 必须是构造器的第一条语句。想在调用父类构造器前做点事(校验参数、准备入参、记日志)?只能用变通:
public Sub(int v) {
super(check(v)); // 把校验塞进静态方法,再当参数传
}
private static int check(int v) {
if (v < 0) throw new IllegalArgumentException();
return v;
}
绕了一圈,逻辑被打散、可读性差。JEP 513 解除该限制——super(...) 之前可以写普通语句(只要不引用未初始化的 this 字段)。
语法#
examples/flexible-constructor/ 真实源码(snippets 嵌入零漂移):
package com.javamodern.flexctr;
/**
* 演示 JDK 25 弹性构造器(JEP 513):可在调用 {@code super(...)} <b>之前</b>写语句。
*
* <p>JDK 8 中 {@code super(...)} 必须是构造器首条语句,要校验参数只能用静态方法变通
* ({@code super(check(v))});JDK 25 起可直接在 {@code super} 前内联校验,逻辑更直观。
*/
public class NonNegativeCounter extends Counter {
public NonNegativeCounter(int initial) {
if (initial < 0) { // (1) super 之前的语句——JDK 25 才允许
throw new IllegalArgumentException("initial must be >= 0, got " + initial);
}
super(initial);
}
}
- :material-lightbulb:
if (initial < 0) throw ...出现在super(initial)之前——JDK 25 才允许。校验失败时super根本不会执行。
运行输出(JDK 25 实测):
initial = 5
与 JDK 8 对比#
public NonNegativeCounter(int initial) {
super(check(initial));
}
private static int check(int v) {
if (v < 0) throw new IllegalArgumentException("initial must be >= 0, got " + v);
return v;
}
public NonNegativeCounter(int initial) {
if (initial < 0)
throw new IllegalArgumentException("initial must be >= 0, got " + initial);
super(initial);
}
底层原理#
JVM 字节码层面,构造器里的 invokespecial(即 super/this 调用)本就没有"必须第一条"的硬约束——这是 Java 语言的限制,不是 JVM 的。JEP 513 放宽的是语言规则:编译器允许在 super/this 前插入语句,编译为正常顺序的字节码。唯一保留的限制:这些前置语句不能读/写尚未初始化的 this 字段(此时父类还没构造,字段状态未定义)。
graph LR
A["进入子类构造器"] --> P["前置语句:校验/准备"]
P --> S["super(...) 调用父类构造器"]
S --> I["初始化本类字段"]
I --> R["构造器体剩余部分"]
style P fill:#c8e6c9
style S fill:#bbdefb
常见坑 / 最佳实践#
- 前置语句不能碰
this字段:父类未构造前,实例字段尚未初始化,读它们是未定义行为(编译器会拒绝)。 this(...)链同理:委托另一个构造器前也能写语句,但同样不能访问this字段。record与显式字段:record 的规范构造器本就宽松,弹性构造器主要利好普通继承场景。- 校验放前置还是后置:需要"父类构造前就拒绝"的场景(如本例),前置校验比静态方法变通更直观。
小结#
弹性构造器移除了一条历史悠久的"形式限制",把校验、准备逻辑放回它们语义上该在的位置——构造器开头、super 之前。代码更直白,少一层静态方法间接。