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() 之后可以
}
}
实际用途:
- 参数校验(避免用静态 helper 方法绕过)
- 参数规范化(如
Stringtrim、集合拷贝) - Record 的紧凑构造器与父类配合
- 更自然的初始化逻辑
与 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 导出的所有包的所有类型 |
⑤ 优先级(从高到低)
- 单类型导入
import java.sql.Date; - 包通配符导入
import java.sql.*; - 模块导入
import module java.sql; 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.concurrentSubtask接口
② 新增 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 内存访问方法 | 弃用 |
六、升级注意事项
sun.misc.Unsafe内存访问方法弃用警告 :Netty、Kafka、Spark 等依赖 Unsafe 的框架会产生大量警告- 检查框架是否有新版本
- 或临时用
--sun-misc-unsafe-memory-access=allow抑制
- 分代 ZGC 成为默认 :若此前显式使用了
-XX:+ZGenerational,该选项现在是无害的(默认已开) - 非分代 ZGC 已弃用:规划迁移
- Markdown 文档注释:适合新代码;混合风格(Markdown + HTML)可能造成不一致
- String Templates 已被搁置:不要在生产代码中使用
- 原始类型模式匹配是预览:注意定义域与支配性检查
七、参考资源
- 官方发布页:https://openjdk.org/projects/jdk/23/
- JEP 474(分代 ZGC 默认):https://openjdk.org/jeps/474
- JEP 467(Markdown 文档注释):https://openjdk.org/jeps/467
- JEP 455(原始类型模式匹配):https://openjdk.org/jeps/455
- JEP 471(弃用 Unsafe 内存访问):https://openjdk.org/jeps/471
- JEP 476(模块导入声明):https://openjdk.org/jeps/476