JDK 13 新特性详解
发布日期 :2019 年 9 月 17 日
规范版本 :Java SE 13(JSR 388)
是否 LTS :否
主题 :文本块首次预览、
yield取代break value、动态 CDS 归档
一、版本总览
JDK 13 是一个「打磨型」版本,共集成 5 个 JEP。核心价值在于:
- 文本块(Text Blocks) 首次预览------解决 Java 多行字符串的历史痛点
- Switch 表达式第二次预览 ------用
yield替换break value - 动态 CDS 归档------应用退出时自动生成归档,AppCDS 体验大幅改善
- 旧版 Socket API 重写------为虚拟线程铺路
二、核心特性详解
1. JEP 355:文本块(Text Blocks,预览)
背景 :Java 长期缺乏多行字符串字面量。写 SQL、JSON、HTML 时充斥着 \n 与 + 拼接,可读性极差。
java
// 旧方式:难以阅读和维护
String html = "<html>\n" +
" <body>\n" +
" <p>Hello, World!</p>\n" +
" </body>\n" +
"</html>\n";
String query = "SELECT id, name, email\n" +
"FROM users\n" +
"WHERE age > 18\n" +
"ORDER BY name";
文本块方式:
java
String html = """
<html>
<body>
<p>Hello, World!</p>
</body>
</html>
""";
String query = """
SELECT id, name, email
FROM users
WHERE age > 18
ORDER BY name
""";
语法规则:
① 定界符:三个双引号 """ + 换行
- 开始定界符
"""后必须换行,否则编译错误 - 结束定界符可以单独一行,也可以在最后一行内容之后
java
String s1 = """
line1
line2"""; // 合法:结束定界符紧跟内容
String s2 = """
line1
line2
"""; // 合法:结束定界符独立一行
String bad = """line1
line2"""; // 编译错误:开始定界符后必须有换行
② 缩进处理(Incidental Whitespace 剥离)
文本块会自动剥离所有行共有的最小缩进,同时保留「有意的额外缩进」。
java
String s = """
a
b
c
""";
// 结果:"a\n b\nc\n"
// 说明:
// - 最小缩进 = 8 空格("a"、"c"、"\"\"\"" 行)
// - "b" 行有 10 空格,剥离 8 后剩 2 → " b"
// - "a"、"c" 剥离 8 后剩 0
③ 转义序列
| 转义 | 含义 |
|---|---|
\n |
换行(普通换行也可直接写在文本块中) |
\t |
制表符 |
\s |
新增:空格(用于保留行尾空格) |
\ + 换行 |
新增:行连接,抑制该处换行 |
\" |
双引号 |
\\ |
反斜杠 |
\uXXXX |
Unicode 转义 |
java
// \s 保留行尾空格
String colored = """
red \s
green\s
""";
// \ 行连接:抑制换行,写超长行
String longLine = """
This is a very long line that \
continues here without a newline.
""";
// 结果:"This is a very long line that continues here without a newline.\n"
④ 末尾换行 :若结束定界符独立成行,结果字符串以 \n 结尾。
java
"""
a
""" // => "a\n"
"""
a""" // => "a"
⑤ 文本块中的 """ 与 \"""
java
String quotes = """
He said \"""Hello\"""
""";
⑥ 文本块是 String 类型
java
// 可用于任何需要 String 的地方
String sql = """
SELECT * FROM t WHERE id = ?
""";
PreparedStatement ps = conn.prepareStatement(sql);
⑦ 文本块与字符串方法
java
String json = """
{"name":"Alice"}
""";
json.strip(); // 去除首尾空白
json.stripIndent(); // 智能剥离缩进(JDK 12 引入)
json.translateEscapes(); // 处理转义序列(JDK 15 引入)
json.formatted("x"); // 等价于 String.format(JDK 15 引入)
启用方式(JDK 13 是预览特性):
bash
javac --enable-preview --release 13 Example.java
java --enable-preview Example
2. JEP 354:Switch 表达式(第二次预览)
在 JDK 12 预览(JEP 325)的基础上,把 break value 改为 yield value。
为什么改? break 在两种场景下语义混淆:
java
// JDK 12:break 既用于跳出 switch,又用于返回值 ------ 容易误解
int x = switch (k) {
case 1:
break 10;
default:
break 20;
};
// 嵌套在循环中时更混乱:
for (int i = 0; i < 10; i++) {
int x = switch (i) {
case 1: break 10; // 这个 break 是跳出循环还是返回值?
default: break 20;
};
}
JDK 13 的 yield:
java
int x = switch (k) {
case 1 -> 10; // 箭头形式直接返回值
case 2 -> { int t = f(); yield t; } // 块形式用 yield
default -> 20;
};
yield 的语义 :终止当前 switch 表达式的求值,把值作为 switch 表达式的结果。
关键规则:
yield只能出现在 switch 表达式的块分支中yield不是 关键字,仍是上下文关键字(int yield = 1;合法)yield与break的区别:break跳出循环 或 switch 语句yield从 switch 表达式返回值
java
// 对比示例
int result = switch (day) {
case MONDAY -> 1;
case FRIDAY -> {
System.out.println("Almost weekend"); // 允许副作用
yield 5; // 必须 yield 值
}
default -> {
yield 0;
}
};
注意 :JDK 13 预览版同时 支持 yield 和(已弃用的)break value,但后者打印弃用警告。JDK 14 正式版中 break value 被移除。
3. JEP 350:动态 CDS 归档(Dynamic CDS Archives)
背景 :JDK 10 的 AppCDS 需要「先试运行 → 生成列表 → dump 归档 → 正式运行」四步。JDK 13 简化为一步:
bash
# JDK 13:运行时指定归档输出路径,退出时自动生成
java -XX:ArchiveClassesAtExit=app.jsa -cp app.jar com.example.Main
# 下次启动直接使用
java -XX:SharedArchiveFile=app.jsa -cp app.jar com.example.Main
工作原理:
- JVM 启动时使用默认 CDS 归档(JDK 核心类)
- 应用运行期间,JVM 动态记录额外加载/链接的类
- JVM 退出时,把这些类与默认归档的内容合并,写出
app.jsa
关键限制:
- 动态归档依赖于创建时的类路径:任何类路径变化都需要重新生成
- 归档与 JDK 版本、CPU 架构绑定
- 不支持在归档中包含自定义类加载器加载的类
与后续版本的关系:
| 版本 | JEP | 内容 |
|---|---|---|
| 10 | 310 | AppCDS(手动) |
| 12 | 341 | 默认 CDS 归档 |
| 13 | 350 | 动态 CDS 归档 |
| 19 | 424 | CDS 归档中存放堆对象(JDK 19 预览) |
| 24 | 483 | AOT Class Loading & Linking(Project Leyden) |
| 25 | 514 | AOT 命令行人体工程学 |
| 26 | 516 | 任意 GC 的 AOT 对象缓存 |
收益:启动时间提升 10%~30%,显著降低延迟敏感的 Serverless 场景冷启动时间。
4. JEP 353:重新实现旧版 Socket API
背景 :java.net.Socket 和 java.net.ServerSocket 的实现自 JDK 1.0 以来几乎没有大变化,代码古老、难以维护,且阻塞式的 Thread 用线程模型不利于未来虚拟线程的实现。
变更:
Socket/ServerSocket的底层实现 (PlainSocketImpl)替换为基于 NIO 的NioSocketImpl- 公共 API 完全不变 ------ 这是纯粹的内部实现替换
- 保留
java.net.SocketImpl抽象类(供 JDK 内部及极少数高级用户使用)
新增系统属性用于回退:
bash
# 回退到旧实现(仅在遇到兼容性问题时使用)
java -Djdk.net.usePlainSocketImpl=true MyApp
收益:
- 更容易维护与修复安全漏洞
- 为虚拟线程(JEP 444)做好准备
- 与 NIO 共享底层代码
配套变更 :JDK 15 的 JEP 373 用同样的思路重写了 DatagramSocket。
5. JEP 351:ZGC 归还未使用内存
问题 :ZGC 在 JDK 11 中不会把未使用的堆内存归还操作系统,导致容器环境中 RSS 持续高位。
变更:ZGC 现在会把未使用内存归还 OS。
bash
# 相关参数
java -XX:+UseZGC -XX:ZUncommitDelay=300 MyApp # 300 秒后归还(默认值)
java -XX:-ZUncommit MyApp # 禁用归还
触发条件:
- 堆中空闲区域连续未使用超过
ZUncommitDelay - 归还后堆最小不低于
-Xms指定值
收益:容器环境内存占用显著降低。
三、JDK 13 特性速查表
| 类别 | JEP | 特性 | 状态 |
|---|---|---|---|
| 语言 | 355 | 文本块 | 预览 |
| 语言 | 354 | Switch 表达式(第二次预览,引入 yield) |
预览 |
| 库 | 353 | 重新实现旧版 Socket API | 正式(内部实现) |
| GC | 351 | ZGC 归还未使用内存 | 正式 |
| 运行时 | 350 | 动态 CDS 归档 | 正式 |
四、升级注意事项
- 文本块与 Switch 表达式仍是预览特性 ,需
--enable-preview,无兼容性保证 break value已弃用 ,改用yield- 动态 CDS 归档与路径强绑定:迁移应用目录或依赖版本时需重新生成
- Socket 实现变更 :极端情况下(依赖旧的实现细节)可能有行为差异;用
-Djdk.net.usePlainSocketImpl=true排查 - ZGC 内存归还:容器内存监控需重新校准
五、参考资源
- 官方发布页:https://openjdk.org/projects/jdk/13/
- JEP 355(文本块):https://openjdk.org/jeps/355
- JEP 354(Switch 表达式):https://openjdk.org/jeps/354
- JEP 350(动态 CDS):https://openjdk.org/jeps/350
- JEP 353(Socket 重写):https://openjdk.org/jeps/353