JEP 526为JDK 26简化了延迟初始化

来源: InfoQ - 后端

原文

面向JDK 26的JEP 526,惰性常量(Lazy Constants,第二轮预览)完成了第二次预览,并整合了首轮预览(即JEP 502,稳定值(Stable Values,预览))之后的修改。该特性最初名为“稳定值(Stable Values)”,现引入了“计算型常量(computed constants)”的概念,即最多仅初始化一次的不可变值容器。它在保留final字段性能与安全性优势的同时,为初始化时机提供了更大的灵活性。本次JEP的主要修订包括:将名称从“Stable Values”更改为“Lazy Constants”,新名称更能准确体现其面向高阶用例的设计意图,并提升该功能可发现性。

早期的Stable Values API暴露了多个底层操作,例如setOrThrow、trySet和orElseSet,用于对初始化过程进行细粒度控制。但根据提案所述,这些操作难以合理地使用,且常常诱使开发者采用违背API初衷的模式。JEP 526以一个更简洁、更符合人体工程学的类java.lang.LazyConstant取代了这些机制,它仅支持基于工厂函数的初始化方式。此次重命名也澄清了设计目标:不再强调“通过底层控制实现稳定性”,而是聚焦于“延迟初始化 + 不可变性”的组合。开发者现在可以表达延迟初始化逻辑,而无需再依赖双重检查锁定(double-checked locking)、Holder类或可空字段等临时性模式。

使用新 API 时,只需提供一个 supplier 函数即可定义一个惰性常量。该值在首次访问后会被缓存,且永不改变。例如:

private static final LazyConstant LOG =
    LazyConstant.of(() -> Logger.create(MyService.class));


void run() {
    LOG.get().info("service started");
}

这种方式取代了传统的懒加载惯用方法(如双重检查锁定、Holder类、可空字段等),后者通常需要额外的状态管理并谨慎处理线程安全问题。而使用 LazyConstant 后,JVM在值初始化完成后会将其视为真正的常量,从而开启传统可变懒加载技术无法实现的优化机会。

开发者还可以通过惰性列表(lazy lists)和惰性映射(lazy maps)来聚合延迟初始化的值,其中每个元素都封装在独立的惰性常量中。这使得集合能够“按需”增长,而非一次性全部创建。例如,应用程序可以维护一个控制器池,而无需提前实例化所有对象:

static final List ORDERS =
    List.ofLazy(POOL_SIZE, _ -> new OrderController());


OrderController controller() {
    long index = Thread.currentThread().threadId() % POOL_SIZE;
    return ORDERS.get((int) index);
}

列表中的每个元素仅在首次访问时初始化,后续调用直接返回缓存的控制器实例。同样的模式也适用于带命名键的惰性映射:

static final Map ORDERS =
    Map.ofLazy(Set.of("Customers", "Internal", "Testing"),
               _ -> new OrderController());


OrderController controller() {
    return ORDERS.get(Thread.currentThread().getName());
}

这些聚合结构在支持更富表现力的访问模式的同时,提供了延迟初始化、线程安全性和JVM优化的能力。

与早期预览版相比,有一个显著变化,那就是,不再允许将null作为计算结果。设计者指出,禁止null可消除语义上的歧义性、避免主路径中的条件分支,并与其他不可变构造(如不可变集合)保持一致。这一改动也简化了心智模型:LazyConstant始终返回一个真实对象;任何试图将null视为合法常量的做法,如今都被视为设计错误。

从“Stable Values”到“Lazy Constants”的转变也体现了设计理念的调整。前者类似于一种底层同步或原子原语,再叠加不可变性;而后者则为日常初始化场景提供了高层抽象。这使得该特性更适用于库和框架开发,它们可利用惰性常量降低那些并非每次运行都会用到的组件的启动开销。此外,将LazyConstant存放在final字段中,还能让JVM在初始化后对该值进行常量折叠(constant folding),从而将不可变性的可靠性与延迟计算的效益融为一体。

JEP 526的首要目标是提升开发者体验,但其性能影响同样显著。许多应用在启动时会初始化庞大的对象图或昂贵资源(如日志器、配置对象、缓存等)。若能将这些资源表示为惰性常量,启动路径将变得更轻量、响应更快。该API还保证即使在并发环境下,初始化也最多只发生一次,且无需开发者手动实现同步逻辑。对于大型系统或模块化架构而言,这类成本节省会迅速累积。

目前该特性仍处于预览阶段,编译和运行时需显式启用--enable-preview设计团队正在征集关于简化后的API接口、命名以及在真实工作负载中适用性的反馈。根据提案,团队预期此次修订更贴近开发者实际需求:机制更少、语义更精准、与JVM优化流水线协同更佳。如果预览阶段验证成功,“惰性常量”有望在未来JDK版本中成为平台的正式组成部分。

随着 Java 持续引入既能减少样板代码、提升性能,又不牺牲安全性的特性,“惰性常量”代表了一种自然演进。一旦正式落地,该特性将为开发者提供一种比数十年来惯用的懒加载模式更可预测、更优化的替代方案,符合Java平台在清晰性、正确性与性能方面的整体目标。

原文链接:

JEP 526 Simplifies Deferred Initialization Ahead of JDK 26