跳转至

弹性构造器(Flexible Constructor Bodies, JEP 513)#

版本信息

为什么需要#

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);
    }
}
  1. :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 之前。代码更直白,少一层静态方法间接。

参考#