JDK 23 新特性详解

JDK 23 新特性详解

发布日期 :2024 年 9 月 17 日

规范版本 :Java SE 23(JSR 398)

是否 LTS :否

主题:分代 ZGC 成为默认、Markdown 文档注释、原始类型模式匹配首次预览


一、版本总览

JDK 23 共集成 12 个 JEP ,没有正式的新语言特性,主要是预览特性的推进与 GC/工具的改进:

JEP 特性 状态
455 Primitive Types in Patterns, instanceof, and switch 首次预览
474 ZGC: Generational Mode by Default 正式
467 Markdown 文档注释 正式
471 弃用 sun.misc.Unsafe 的内存访问方法 弃用
466 Class-File API 第二次预览
473 Stream Gatherers 第二次预览
476 Module Import Declarations 首次预览
477 Implicitly Declared Classes and Instance Main Methods 第三次预览
480 Structured Concurrency 第三次预览
481 Scoped Values 第三次预览
482 Flexible Constructor Bodies 第二次预览
469 Vector API 第八次孵化

注意 :String Templates(JEP 459)在 JDK 23 中被搁置,未继续预览。


二、语言特性

1. JEP 455:原始类型模式匹配(首次预览)

目标 :让 instanceof 与 switch 支持所有原始类型(不仅是引用类型的装箱)。

此前的问题:

java 复制代码
// 不能直接对原始类型做模式匹配
if (obj instanceof int i) { }      // JDK 22 及以前:编译错误

// 必须使用装箱类型
if (obj instanceof Integer i) {
    int value = i;                 // 自动拆箱,可能抛 NPE
}

// switch 也不支持原始类型模式
switch (obj) {
    case Integer i -> ...;         // 只能这样
    // case int i -> ...;          // ❌ 之前不支持
}

JDK 23 的能力:

java 复制代码
// ① switch 中匹配原始类型
static String describe(Object obj) {
    return switch (obj) {
        case int i when i > 0     -> "positive int: " + i;
        case int i                -> "int: " + i;
        case long l               -> "long: " + l;
        case double d             -> "double: " + d;
        case float f              -> "float: " + f;
        case boolean b            -> "boolean: " + b;
        case char c               -> "char: " + c;
        case byte b               -> "byte: " + b;
        case short s              -> "short: " + s;
        default                   -> "other";
    };
}

// ② instanceof 中匹配原始类型
if (obj instanceof int i && i > 100) {
    System.out.println("large int: " + i);
}

// ③ 定义域覆盖的穷尽性
static String parity(int i) {
    return switch (i) {
        case 0            -> "zero";
        case int n when n % 2 == 0 -> "even";
        case int n        -> "odd";
    };
}

自动装箱/拆箱的优化:

java 复制代码
// JDK 23:当无法拆箱时(如值超出范围),模式不匹配
Object obj = 300L;
if (obj instanceof int i) {
    // obj 是 Long,不匹配 int 模式
    System.out.println("int: " + i);
} else {
    System.out.println("not an int");   // 走这里
}

重要:定义域检查(Dominance)

java 复制代码
// 编译器会检查模式的定义域
switch (obj) {
    case int i -> ...;
    case Integer _ -> ...;      // ❌ 编译错误:int 匹配所有 Integer,后者被支配
}

实际价值:

  • 消除自动装箱的性能开销与 NPE 风险
  • 让 switch 能处理所有基本类型
  • 为将来的值类型(Project Valhalla) 铺路

启用方式:

bash 复制代码
javac --enable-preview --release 23 Example.java
java --enable-preview Example

演进路径:

版本 JEP 状态
23 455 预览
24 488 第二次预览
25 507 第三次预览
26 530 第四次预览
27 532 第五次预览

(截至 JDK 27 仍未转正。)

2. JEP 482:灵活构造器体(第二次预览)

(JDK 22 的 JEP 447 首次预览。)

问题 :Java 要求在构造器中,super(...) 或 this(...) 调用必须是第一条语句。这导致无法在调用父构造器前做参数校验:

java 复制代码
public class PositiveInt extends Number {
    private final int value;

    public PositiveInt(int value) {
        // ❌ 无法在 super() 之前做校验
        if (value < 0) throw new IllegalArgumentException();

        this.value = value;
        super(value);    // 必须第一条
    }
}

变通方案的丑陋之处:

java 复制代码
// 方案 1:静态工厂方法
public PositiveInt(int value) {
    super(checkPositive(value));   // 校验挪到 helper
}
private static int checkPositive(int value) {
    if (value < 0) throw new IllegalArgumentException();
    return value;
}
// 问题:不是所有场景都能这样(如需要初始化多个字段)

// 方案 2:把校验放到构造器体内的 super() 之后
public PositiveInt(int value) {
    super(value);                  // 先执行父类构造器
    if (value < 0) throw new IllegalArgumentException();   // 太晚了
    this.value = value;
}
// 问题:父类构造器已经用非法值做了事

JDK 22/23 的解决方案:

java 复制代码
public class PositiveInt extends Number {
    private final int value;

    public PositiveInt(int value) {
        // ✅ 允许在 super() 之前执行语句,只要不访问 this
        if (value < 0) {
            throw new IllegalArgumentException("must be >= 0: " + value);
        }

        // ✅ 可以声明局部变量
        int normalized = value == 0 ? 0 : value;

        super(normalized);

        this.value = normalized;
    }
}

允许的「前置语句」:

  • 局部变量声明与初始化
  • 表达式语句(不访问 this)
  • if / for / while / switch 等控制流
  • throw 语句
  • 不引用 this 的语句

禁止的操作 (在 super() 之前):

  • 访问 this 的实例字段
  • 调用 this 的实例方法
  • 使用 this 作为参数
  • 使用 super.field / super.method()
java 复制代码
public class Child extends Parent {
    private int x;

    public Child(int x) {
        // ❌ 编译错误
        this.x = x;              // 不能访问实例字段
        doSomething();           // 不能调用实例方法
        System.out.println(this); // 不能使用 this

        // ✅ 允许
        int temp = x * 2;        // 局部变量
        if (temp < 0) throw new IllegalArgumentException();

        super(temp);
        this.x = x;              // super() 之后可以
    }
}

实际用途:

  1. 参数校验(避免用静态 helper 方法绕过)
  2. 参数规范化(如 String trim、集合拷贝)
  3. Record 的紧凑构造器与父类配合
  4. 更自然的初始化逻辑

与 Record 的关系:

java 复制代码
// Record 的紧凑构造器也可以使用
record Range(int lo, int hi) {
    Range {
        if (lo > hi) {
            throw new IllegalArgumentException(
                "lo(%d) > hi(%d)".formatted(lo, hi));
        }
        // 紧凑构造器允许字段赋值
        lo = Math.min(lo, hi);
        hi = Math.max(lo, hi);
    }
}

演进路径:

版本 JEP 状态
22 447 预览(名为 Statements before super(...))
23 482 第二次预览(改名 Flexible Constructor Bodies)
24 492 第三次预览
25 513 正式

3. JEP 476:模块导入声明(首次预览)

目标 :允许导入整个模块 导出的所有包,简化 import。

java 复制代码
// 传统方式:需要逐个导入
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

// JDK 23:导入整个模块
import module java.base;

// 之后可直接使用 java.base 导出的所有类型
List<String> list = List.of("a", "b");
Map<String, Integer> map = Map.of("a", 1);
Set<Integer> set = Set.of(1, 2, 3);
Stream<String> stream = list.stream();

关键规则:

① 只导入模块 导出的包 (exports 的包),不含内部包

java 复制代码
import module java.base;    // 导入 java.lang、java.util、java.io、java.net 等

② 不导入子模块

java 复制代码
import module java.se;      // java.se 聚合了 java.base、java.sql 等
// 只导入 java.se 自己导出的包,不含 java.sql

③ 名称冲突的处理

java 复制代码
import module java.base;      // 含 java.util.Date
import module java.sql;       // 含 java.sql.Date
// ⚠️ Date 有歧义

// 解决:显式单类型导入优先
import java.sql.Date;
import module java.base;
import module java.sql;
// 现在 Date 明确是 java.sql.Date

④ 与通配符导入的关系

导入方式 含义
import java.util.*; 导入包 java.util 的所有类型
import module java.base; 导入模块 java.base 导出的所有包的所有类型

⑤ 优先级(从高到低)

  1. 单类型导入 import java.sql.Date;
  2. 包通配符导入 import java.sql.*;
  3. 模块导入 import module java.sql;
  4. java.lang 的隐式导入

典型用途:

java 复制代码
// 教学示例、原型代码、脚本
import module java.base;
import module java.sql;

void main() throws Exception {
    var conn = DriverManager.getConnection("jdbc:h2:mem:test");
    var stmt = conn.createStatement();
    var rs = stmt.executeQuery("SELECT 1");
    while (rs.next()) {
        System.out.println(rs.getInt(1));
    }
}

演进路径:

版本 JEP 状态
23 476 预览
24 494 第二次预览
25 511 正式

4. JEP 477:隐式声明类与实例 main 方法(第三次预览)

(JDK 21 JEP 445 首次,JDK 22 JEP 463 第二次。)

JDK 23 的变化:

  • 允许隐式声明类中包含更多结构(如顶层字段、方法)
  • 改进自动导入的范围
java 复制代码
// 隐式声明类:无 class 声明
String greeting = "Hello";        // 顶层字段(自动成为 static)

void main() {
    System.out.println(greeting);
    System.out.println(add(1, 2));
}

int add(int a, int b) {           // 顶层方法
    return a + b;
}

(JDK 25 的 JEP 512 转正。)

5. JEP 480:结构化并发(第三次预览)

相比 JDK 22(JEP 462)的改进:

① API 稳定化

  • StructuredTaskScope 位于 java.util.concurrent
  • Subtask 接口

② 新增 allSuccessfulOrThrow() 方法

java 复制代码
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Subtask<String> user = scope.fork(() -> findUser());
    Subtask<Integer> order = scope.fork(() -> fetchOrder());

    // 一行完成 join + 失败检查
    scope.join().throwIfFailed();

    // JDK 23 新增的便捷方法
    // scope.allSuccessfulOrThrow();

    return new Response(user.get(), order.get());
}

③ StructuredTaskScope 的配置

java 复制代码
// 自定义线程工厂
try (var scope = new StructuredTaskScope.ShutdownOnFailure(
        "my-scope", Thread.ofVirtual().factory())) {
    // ...
}

6. JEP 481:Scoped Values(第三次预览)

相比 JDK 22(JEP 464):

  • API 稳定化
  • 改进与结构化并发的协同
  • 文档与错误消息完善
java 复制代码
import java.lang.ScopedValue;

public class RequestContext {
    public static final ScopedValue<User> USER = ScopedValue.newInstance();
    public static final ScopedValue<Trace> TRACE = ScopedValue.newInstance();

    static void handle(Request req) {
        User user = authenticate(req);
        Trace trace = new Trace();

        ScopedValue.where(USER, user)
                   .where(TRACE, trace)
                   .run(() -> process(req));
    }

    static void process(Request req) {
        // 深层调用中直接读取,无需参数传递
        System.out.println("user = " + USER.get() + ", trace = " + TRACE.get());
    }
}

三、GC 与运行时

7. JEP 474:分代 ZGC 成为默认模式

背景 :ZGC 在 JDK 21(JEP 439)引入分代模式,但非默认。经过两个版本的验证,JDK 23 中分代模式成为默认。

bash 复制代码
# JDK 21/22:需要显式开启分代
java -XX:+UseZGC -XX:+ZGenerational MyApp

# JDK 23+:分代是默认
java -XX:+UseZGC MyApp

# 如需回到非分代(已弃用,将在 JDK 24 移除)
java -XX:+UseZGC -XX:-ZGenerational MyApp
# => 警告:The NonGenerational mode of ZGC is deprecated and will be removed

为什么分代更好:

维度 非分代 ZGC 分代 ZGC
扫描范围 整个堆 优先年轻代
分配停滞风险 较高 低
堆内存需求 大 小(通常 50% 即可)
吞吐量 较低 显著高
停顿时间 亚毫秒 亚毫秒(保持)

推荐配置:

bash 复制代码
# 低延迟场景(推荐)
java -XX:+UseZGC -XX:ZAllocationSpareThreads=2 MyApp

# 有严格内存约束的容器
java -XX:+UseZGC -XX:+UseCompressedOops -Xmx4g MyApp

8. JEP 471:弃用 sun.misc.Unsafe 的内存访问方法

背景 :sun.misc.Unsafe 中的内存访问方法(allocateMemory、freeMemory、getInt、putInt 等)是 JVM 内部使用的,不是公共 API。它们不安全(无边界检查)、不可移植(在不同 GC 下行为不同),并且阻碍了 JVM 的演进(如 Valhalla 的值类型)。

JDK 23 中的行为:

java 复制代码
import sun.misc.Unsafe;

Unsafe unsafe = getUnsafe();

// ⚠️ 以下方法现在打印弃用警告
unsafe.allocateMemory(1024);
unsafe.freeMemory(addr);
unsafe.getInt(addr);
unsafe.putInt(addr, 42);
unsafe.getLong(addr);
unsafe.putLong(addr, 42L);
unsafe.copyMemory(...);
unsafe.setMemory(...);
unsafe.addressSize();
unsafe.pageSize();
unsafe.allocateInstance(Class);   // 此方法不受影响
unsafe.objectFieldOffset(Field);  // 此方法不受影响
unsafe.compareAndSwapInt(...);    // 此方法不受影响
text 复制代码
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::allocateMemory has been called by com.example.Main
WARNING: Please consider reporting this to the maintainers of com.example.Main
WARNING: sun.misc.Unsafe::allocateMemory will be removed in a future release

开关控制:

bash 复制代码
# 收到警告(默认)
java MyApp

# 收到警告并附带堆栈
java -Xlog:deprecation=info MyApp

# 抑制警告(不推荐)
java --sun-misc-unsafe-memory-access=allow MyApp

# 禁止使用(提前适配)
java --sun-misc-unsafe-memory-access=deny MyApp

替代方案:

Unsafe 方法 替代
allocateMemory / freeMemory Arena.allocate(FFM API)
getInt / putInt / getLong / putLong MemorySegment.get/set + ValueLayout
copyMemory MemorySegment.copy
setMemory MemorySegment.fill
compareAndSwapInt VarHandle.compareAndSet
objectFieldOffset VarHandle 或 MethodHandles.Lookup.findVarHandle
allocateInstance 反射 Constructor.newInstance(无替代)
park / unpark LockSupport
throwException 无

迁移示例:

java 复制代码
// ❌ 旧方式
long addr = unsafe.allocateMemory(1024);
unsafe.putInt(addr, 42);
int value = unsafe.getInt(addr);
unsafe.freeMemory(addr);

// ✅ 新方式(FFM API)
try (Arena arena = Arena.ofConfined()) {
    MemorySegment seg = arena.allocate(1024);
    seg.set(ValueLayout.JAVA_INT, 0, 42);
    int value = seg.get(ValueLayout.JAVA_INT, 0);
}

注意 :sun.misc.Unsafe 中非内存访问 的方法(如 compareAndSwapInt)在 JDK 23 中不受影响 ,但它们是另一轮弃用的目标。长期来看,Unsafe 的几乎所有能力都会有标准替代。

后续演进:

版本 JEP 内容
23 471 弃用内存访问方法
24 498 使用内存访问方法时警告
26 --- 可能开始拒绝

9. JEP 469:Vector API(第八次孵化)

继续孵化。JDK 23 中的改进主要是 API 打磨与文档完善。


四、工具与文档

10. JEP 467:Markdown 文档注释

目标 :允许在 Javadoc 中使用 Markdown 语法,替代繁琐的 HTML 标签。

传统 Javadoc(HTML):

java 复制代码
/**
 * Returns the {@code String} representation of this object.
 * <p>
 * The returned string is composed of:
 * <ul>
 *   <li>the class name</li>
 *   <li>the hash code</li>
 * </ul>
 * <p>
 * Example:
 * <pre>{@code
 * Object obj = new Object();
 * String s = obj.toString();
 * }</pre>
 *
 * @param prefix the prefix to use
 * @return the string representation
 * @throws IllegalArgumentException if {@code prefix} is {@code null}
 * @see java.lang.Object#hashCode()
 */

Markdown 文档注释(JDK 23):

java 复制代码
/// Returns the `String` representation of this object.
///
/// The returned string is composed of:
///
/// - the class name
/// - the hash code
///
/// Example:
///
/// ```java
/// Object obj = new Object();
/// String s = obj.toString();
/// ```
///
/// @param prefix the prefix to use
/// @return the string representation
/// @throws IllegalArgumentException if `prefix` is `null`
/// @see java.lang.Object#hashCode()

语法规则:

元素 语法
文档注释 ///(三斜杠),每行一个
代码 反引号 code
代码块 三个反引号 + 语言名
列表 -、*、1.
标题 #、##、###
链接 [text](url) 或 [text]
粗体 **bold**
斜体 *italic*
块引用 >
表格 标准 Markdown 表格

块标签在 Markdown 中仍可用:

java 复制代码
/// Adds two numbers.
///
/// @param a the first number
/// @param b the second number
/// @return the sum of `a` and `b`
public static int add(int a, int b) { return a + b; }

内联标签:

java 复制代码
/// Returns the {@link java.util.List} of items.
/// The {@code null} check is performed.

注意:

  • /// 与 /** */ 不能混用在同一个文档注释中
  • /// 是块注释 ,//// 是普通注释
  • HTML 标签仍可在 Markdown 中使用(含 <p>、<ul> 等)
  • 生成的 HTML 与原 Javadoc 一致

与 JEP 413({@snippet})结合:

java 复制代码
/// Example usage:
///
/// {@snippet file="Example.java" region="demo"}

或

java 复制代码
/// ```java
/// var order = Order.create("item", 3);
/// ```

收益:

  • 更易读写(无需记 HTML 标签)
  • 与大多数 Markdown 工具兼容
  • 减少 {@code} 的重复输入

11. JEP 466:Class-File API(第二次预览)

相比 JDK 22(JEP 457)的改进:

  • API 微调(Attribute、Signature 等)
  • 更好的 CodeBuilder 支持
  • 增强对新增 class 文件特性的支持
java 复制代码
import java.lang.classfile.*;
import java.lang.constant.*;

// 解析
ClassModel cm = ClassFile.of().parse(Path.of("MyClass.class"));

// 遍历方法
cm.methods().forEach(m -> System.out.println(m.methodName()));

// 生成
byte[] bytes = ClassFile.of().build(
        ClassDesc.of("com.example.Gen"),
        cb -> cb.withMethod(
                "hello",
                MethodTypeDesc.of(ConstantDescs.CD_String),
                ClassFile.ACC_PUBLIC,
                mb -> mb.withCode(c -> c
                        .ldc("Hello")
                        .areturn()
                )
        )
);

演进路径:

版本 JEP 状态
22 457 预览
23 466 第二次预览
24 484 正式

12. JEP 473:Stream Gatherers(第二次预览)

相比 JDK 22(JEP 461):

  • Gatherer 接口的 API 稳定化
  • 内置 Gatherers 的完善(windowFixed、windowSliding、fold、scan、mapConcurrent)
  • 改进并行支持
java 复制代码
import java.util.stream.Gatherers;

// 固定窗口
Stream.of(1, 2, 3, 4, 5)
      .gather(Gatherers.windowFixed(2))
      .toList();
// [[1,2], [3,4], [5]]

// 滑动窗口
Stream.of(1, 2, 3, 4, 5)
      .gather(Gatherers.windowSliding(3))
      .toList();
// [[1,2,3], [2,3,4], [3,4,5]]

// 折叠
Stream.of("a", "b", "c")
      .gather(Gatherers.fold(() -> "", String::concat))
      .findFirst();
// "abc"

// 累积扫描
Stream.of(1, 2, 3, 4)
      .gather(Gatherers.scan(() -> 0, Integer::sum))
      .toList();
// [1, 3, 6, 10]

// 并发映射(用虚拟线程)
urls.stream()
    .gather(Gatherers.mapConcurrent(64, this::fetch))
    .toList();

演进路径:

版本 JEP 状态
22 461 预览
23 473 第二次预览
24 485 正式

五、JDK 23 特性速查表

类别 JEP 特性 状态
语言 455 原始类型模式匹配 首次预览
语言 482 灵活构造器体 第二次预览
语言 476 模块导入声明 首次预览
语言 477 隐式声明类与实例 main 第三次预览
GC 474 分代 ZGC 成为默认 正式
工具 467 Markdown 文档注释 正式
工具 466 Class-File API 第二次预览
工具 473 Stream Gatherers 第二次预览
工具 469 Vector API 第八次孵化
并发 480 结构化并发 第三次预览
并发 481 Scoped Values 第三次预览
弃用 471 弃用 Unsafe 内存访问方法 弃用

六、升级注意事项

  1. sun.misc.Unsafe 内存访问方法弃用警告 :Netty、Kafka、Spark 等依赖 Unsafe 的框架会产生大量警告
    • 检查框架是否有新版本
    • 或临时用 --sun-misc-unsafe-memory-access=allow 抑制
  2. 分代 ZGC 成为默认 :若此前显式使用了 -XX:+ZGenerational,该选项现在是无害的(默认已开)
  3. 非分代 ZGC 已弃用:规划迁移
  4. Markdown 文档注释:适合新代码;混合风格(Markdown + HTML)可能造成不一致
  5. String Templates 已被搁置:不要在生产代码中使用
  6. 原始类型模式匹配是预览:注意定义域与支配性检查

七、参考资源


← JDK 22 | 返回索引 | JDK 24 →

相关推荐
code2cat1 小时前
Java高级篇之Hibernate:让对象映射到表,理解持久化与事务
java·后端·hibernate·1024程序员节
骑着蜗牛撵大象3272 小时前
飞牛fnOS部署Damselfly:Compose编排、照片目录与7100固定访问
java·docker·rpc·dubbo·fnos·照片管理·damselfly
金銀銅鐵3 小时前
[Java] 用 CLI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
Nuanyt3 小时前
Hot 100 刷题笔记 · 数组与哈希专题(3 题)
java·笔记·算法·leetcode·哈希算法·1024程序员节
xieliyu.3 小时前
【JavaEE进阶】Spring Boot 日志:从 System.out 到 @Slf4j,6 个级别 + 4 类配置一篇讲透
java·spring boot·spring·java-ee·springboot·logback·slf4j
IT_Octopus4 小时前
Apollo 配置中心入门:一所“小学校“读懂 Portal、Meta Server、Config Service
java·后端·spring
cfm_29144 小时前
偏向锁的Epoch优化机制
java
CRMEB系统商城4 小时前
家电零售的供应商协同与物流提效
java·开发语言·人工智能·小程序·1024程序员节
jason.zeng@15022074 小时前
Nginx 配置详解(静态 HTML、下载目录、API 反向代理三套场景)
java·vue.js·1024程序员节