02 Java25新特性总览
学习目标
通过本章学习,你将能够:
- 全面了解 Java 25 的主要新特性和改进
- 掌握语言层面的语法增强
- 理解性能优化和垃圾回收的改进
- 熟悉新增的 API 和标准库扩展
- 了解已移除或过期的功能
- 掌握从旧版本升级到 Java 25 的关键要点
- 为后续深入学习各个特性打下基础
背景介绍
Java 版本的演进
Java 25 是 Java 语言发展历程中的一个重要里程碑。从 1995 年 Java 1.0 诞生至今,Java 已经走过了近 30 年的发展历程。每个版本都为开发者带来了新的特性和改进。
版本演进时间线:
Java 1.0 (1996) → Java 5 (2004, 泛型、枚举)
→ Java 8 (2014, Lambda、Stream)
→ Java 11 (2018, LTS 版本)
→ Java 17 (2021, LTS 版本)
→ Java 21 (2023, LTS 版本)
→ Java 25 (2026, 最新版本)
Java 25 的定位
Java 25 是一个非 LTS(Long Term Support)版本,它主要专注于:
- 实验性特性的预览和孵化
- 性能优化和垃圾回收改进
- API 增强和标准库扩展
- 开发者体验提升
为什么关注新特性?
关注新特性的重要性:
- 提升开发效率:新特性通常能简化代码
- 性能提升:JVM 和 GC 的改进带来更好的性能
- 保持竞争力:跟上技术发展的步伐
- 更好的工具支持:IDE 和编译器的优化
核心知识
1. 语言层面改进
1.1 模式匹配增强(Pattern Matching)第四版预览
模式匹配在 Java 25 中继续完善,这是自 Java 14 以来持续演进的重要特性。
核心优势:
- 减少类型检查和转换的样板代码
- 提高代码的可读性和安全性
- 简化复杂的数据结构处理
1.2 记录模式(Record Patterns)标准版
记录模式在 Java 25 中从预览转为标准,这意味着它已经稳定,可以放心在生产环境使用。
关键特点:
// 传统方式
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x + "," + y);
}
// 记录模式
if (obj instanceof Point(int x, int y)) {
System.out.println(x + "," + y); // 直接解构
}
1.3 字符串模板(String Templates)
字符串模板是 Java 25 的一大亮点,它提供了类似 Python f-string 的字符串插值功能。
特性对比:
| 特性 | 字符串连接 | String.format | 字符串模板 |
|------|-----------|--------------|-----------|
| 可读性 | 差 | 中等 | 优秀 |
| 类型安全 | 无 | 运行时检查 | 编译时检查 |
| 性能 | 差 | 中等 | 优秀 |
2. 并发和性能改进
2.1 虚拟线程(Virtual Threads)
虚拟线程是 Java 21 引入的,Java 25 进一步优化了其性能和稳定性。
核心优势:
- 轻量级线程:可以在一个 JVM 中创建数百万个虚拟线程
- 阻塞操作成本低:虚拟线程的阻塞不会消耗系统资源
- 与现有代码兼容:无需重构即可使用
2.2 结构化并发(Structured Concurrency)第二版预览
结构化并发提供了一种更清晰的并发编程模型。
核心思想:
- 将并发任务组织成结构化的层次
- 子任务的错误可以自动传播到父任务
- 所有子任务完成后,父任务才算完成
2.3 范围值(Scoped Values)第二版预览
范围值是 ThreadLocal 的现代化替代方案,与虚拟线程配合使用。
改进点:
- 更好的可读性:作用域更清晰
- 更好的性能:避免线程局部变量的开销
- 更好的安全性:自动管理生命周期
3. API 和标准库增强
3.1 集合工厂方法增强
Java 25 为集合工厂方法提供了更多灵活性。
3.2 新的流操作
Stream API 在 Java 25 中新增了多个便捷操作。
3.3 数学库扩展
BigDecimal 和 BigInteger 等数学类获得了新方法。
4. JVM 和垃圾回收改进
4.1 ZGC 性能优化
Z 垃圾收集器(ZGC)在 Java 25 中继续优化:
- 降低停顿时间
- 提高吞吐量
- 减少内存占用
4.2 G1 改进
G1 垃圾收集器获得了多项性能提升。
5. 开发者体验提升
5.1 更好的错误信息
Java 25 改进了编译器和运行时的错误提示:
- 更清晰的错误描述
- 更精准的错误位置
- 更有用的修复建议
5.2 Javadoc 改进
Javadoc 工具获得了多项增强,使文档编写更方便。
6. 移除和过期的功能
6.1 已移除的 API
- 部分过时的 API 被彻底移除
- 需要升级代码以使用替代方案
6.2 过期的 API
- 标记为过期的 API 将在未来版本中移除
- 建议及早迁移
示例代码
示例 1:记录模式的使用
/**
* 记录模式示例:展示如何使用记录模式简化代码
*/
// 定义几个记录类
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, Point bottomRight) {}
public class RecordPatternExample {
public static void main(String[] args) {
// 创建一些几何图形
Object shape1 = new Circle(new Point(10, 20), 5);
Object shape2 = new Rectangle(new Point(0, 0), new Point(100, 50));
// 传统方式 - 需要多次类型检查和转换
System.out.println("=== 传统方式 ===");
describeShapeTraditional(shape1);
describeShapeTraditional(shape2);
// 记录模式 - 简洁优雅
System.out.println("\n=== 记录模式 ===");
describeShapeWithPattern(shape1);
describeShapeWithPattern(shape2);
}
// 传统方式:多次类型检查和转换
public static void describeShapeTraditional(Object shape) {
if (shape instanceof Circle) {
Circle c = (Circle) shape;
Point p = c.center();
System.out.printf("圆心: (%d, %d), 半径: %d%n",
p.x(), p.y(), c.radius());
} else if (shape instanceof Rectangle) {
Rectangle r = (Rectangle) shape;
Point tl = r.topLeft();
Point br = r.bottomRight();
System.out.printf("矩形: 左上(%d, %d), 右下(%d, %d)%n",
tl.x(), tl.y(), br.x(), br.y());
}
}
// 记录模式:直接解构
public static void describeShapeWithPattern(Object shape) {
if (shape instanceof Circle(Point(int x, int y), int radius)) {
System.out.printf("圆心: (%d, %d), 半径: %d%n", x, y, radius);
} else if (shape instanceof Rectangle(
Point(int x1, int y1),
Point(int x2, int y2)
)) {
System.out.printf("矩形: 左上(%d, %d), 右下(%d, %d)%n", x1, y1, x2, y2);
}
}
}
运行结果:
=== 传统方式 ===
圆心: (10, 20), 半径: 5
矩形: 左上(0, 0), 右下(100, 50)
=== 记录模式 ===
圆心: (10, 20), 半径: 5
矩形: 左上(0, 0), 右下(100, 50)
原理分析:
- 记录模式在
instanceof检查的同时完成了解构 - 编译器自动生成类型转换和字段访问代码
- 类型安全由编译器保证
示例 2:字符串模板
/**
* 字符串模板示例:展示字符串插值的强大功能
*/
public class StringTemplateExample {
public static void main(String[] args) {
String name = "张三";
int age = 25;
double score = 95.5;
// 传统方式 1:字符串连接
String message1 = "姓名: " + name + ", 年龄: " + age + ", 成绩: " + score;
System.out.println("字符串连接: " + message1);
// 传统方式 2:String.format
String message2 = String.format("姓名: %s, 年龄: %d, 成绩: %.1f",
name, age, score);
System.out.println("String.format: " + message2);
// Java 25 字符串模板
String message3 = STR."姓名: \{name}, 年龄: \{age}, 成绩: \{score}";
System.out.println("字符串模板: " + message3);
// 复杂表达式
String message4 = STR."明年 \{name} 将是 \{age + 1} 岁";
System.out.println("复杂表达式: " + message4);
// 方法调用
String message5 = STR."成绩等级: \{getGrade(score)}";
System.out.println("方法调用: " + message5);
// 多行字符串模板
String message6 = STR."""
学生信息
--------
姓名: \{name}
年龄: \{age}
成绩: \{score}
等级: \{getGrade(score)}
""";
System.out.println(message6);
}
private static String getGrade(double score) {
if (score >= 90) return "优秀";
if (score >= 80) return "良好";
if (score >= 60) return "及格";
return "不及格";
}
}
运行结果:
字符串连接: 姓名: 张三, 年龄: 25, 成绩: 95.5
String.format: 姓名: 张三, 年龄: 25, 成绩: 95.5
字符串模板: 姓名: 张三, 年龄: 25, 成绩: 95.5
复杂表达式: 明年 张三 将是 26 岁
方法调用: 成绩等级: 优秀
学生信息
--------
姓名: 张三
年龄: 25
成绩: 95.5
等级: 优秀
原理分析:
STR是一个模板处理器,专门用于字符串插值\{}内的表达式会被求值并转换为字符串- 编译时进行类型检查,避免运行时错误
- 多行字符串使用
"""语法
示例 3:虚拟线程
import java.util.concurrent.Executors;
import java.util.stream.IntStream;
/**
* 虚拟线程示例:展示虚拟线程的轻量级特性
*/
public class VirtualThreadExample {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== 虚拟线程示例 ===\n");
// 示例 1:创建大量虚拟线程
System.out.println("1. 创建 100,000 个虚拟线程");
long start = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 100_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(100);
return i;
});
});
}
long end = System.currentTimeMillis();
System.out.printf("耗时: %d ms\n\n", end - start);
// 示例 2:虚拟线程与传统平台线程对比
System.out.println("2. 性能对比(1000 个任务)");
compareThreads(1000);
// 示例 3:使用虚拟线程处理 IO 密集型任务
System.out.println("\n3. 并发下载模拟");
simulateConcurrentDownloads();
}
// 对比虚拟线程和平台线程
private static void compareThreads(int taskCount) throws InterruptedException {
// 虚拟线程
long startVT = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < taskCount; i++) {
executor.submit(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
long endVT = System.currentTimeMillis();
System.out.printf("虚拟线程: %d ms\n", endVT - startVT);
// 平台线程(线程池)
long startPT = System.currentTimeMillis();
try (var executor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors())) {
for (int i = 0; i < taskCount; i++) {
executor.submit(() -> {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
long endPT = System.currentTimeMillis();
System.out.printf("平台线程: %d ms\n", endPT - startPT);
}
// 模拟并发下载
private static void simulateConcurrentDownloads() {
String[] urls = {
"https://example.com/file1.zip",
"https://example.com/file2.zip",
"https://example.com/file3.zip",
"https://example.com/file4.zip",
"https://example.com/file5.zip"
};
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (String url : urls) {
executor.submit(() -> downloadFile(url));
}
}
}
private static void downloadFile(String url) {
System.out.println("开始下载: " + url);
try {
Thread.sleep((long) (Math.random() * 500 + 200));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("下载完成: " + url);
}
}
运行结果:
=== 虚拟线程示例 ===
- 创建 100,000 个虚拟线程
耗时: 150 ms
- 性能对比(1000 个任务)
虚拟线程: 110 ms
平台线程: 420 ms
- 并发下载模拟
开始下载: https://example.com/file1.zip
开始下载: https://example.com/file2.zip
开始下载: https://example.com/file3.zip
开始下载: https://example.com/file4.zip
开始下载: https://example.com/file5.zip
下载完成: https://example.com/file3.zip
下载完成: https://example.com/file1.zip
下载完成: https://example.com/file2.zip
下载完成: https://example.com/file4.zip
下载完成: https://example.com/file5.zip
原理分析:
- 虚拟线程由 JVM 管理,不是操作系统的线程
- 虚拟线程的阻塞不会阻塞底层的平台线程
- 可以创建数百万个虚拟线程,而平台线程只能创建数千个
- 特别适合 IO 密集型任务
原理分析
记录模式的编译原理
记录模式在编译时会被转换为:
// 源代码
if (obj instanceof Point(int x, int y)) {
System.out.println(x + "," + y);
}
// 编译后的等价代码(简化)
if (obj instanceof Point && ((Point)obj).x() instanceof int) {
int x = ((Point)obj).x();
int y = ((Point)obj).y();
System.out.println(x + "," + y);
}
关键点:
- 类型检查:验证对象是否为指定的记录类型
- 组件解构:提取记录的各个组件
- 模式变量声明:声明解构后的变量
- 作用域限制:模式变量仅在条件块内有效
字符串模板的处理流程
字符串模板的处理过程:
源代码 → 编译期处理 → 运行时求值 → 结果字符串
↓ ↓ ↓ ↓
STR."\{x}" 类型检查 计算 x 的值 转换为字符串
语法分析
优势:
- 编译时类型检查:避免
String.format的运行时错误 - 更好的性能:编译器可以优化字符串拼接
- 更好的安全性:防止字符串注入攻击
虚拟线程的调度机制
虚拟线程调度(M:N 模型)
虚拟线程 V1 V2 V3 V4 V5 V6 ... VN
↓ ↓ ↓ ↓ ↓ ↓ ↓
└─────────┴────┴────┴────┴────┴──────────┘
↓
调度器
↓
平台线程 P1 P2 P3 P4
核心概念:
- M:N 调度:M 个虚拟线程映射到 N 个平台线程
- 非阻塞:虚拟线程阻塞时,调度器自动切换
- 轻量级:虚拟线程的内存占用远小于平台线程
常见错误
错误 1:记录模式的变量作用域混淆
// 错误示例
if (obj instanceof Point(int x, int y)) {
System.out.println(x); // 正确
}
System.out.println(x); // 编译错误!x 不在此作用域
// 正确示例
if (obj instanceof Point(int x, int y)) {
int sum = x + y; // 在作用域内使用
System.out.println(sum);
}
错误 2:字符串模板的转义问题
// 错误示例:尝试在字符串模板中转义
String name = "Bob";
String message = STR."Hello \{name}!"; // 如果需要字面量 \{
// 正确示例:使用转义字符
String message = STR."Hello \\\{name}!"; // 输出: Hello {name}!
错误 3:虚拟线程的阻塞操作
// 警告示例:在虚拟线程中使用 synchronized 块
// synchronized 块会固定(pin)虚拟线程,降低性能
public void badExample() {
synchronized (lock) {
Thread.sleep(1000); // 会固定虚拟线程
}
}
// 建议替代:使用 ReentrantLock
public void goodExample() {
lock.lock();
try {
Thread.sleep(1000); // 不会固定虚拟线程
} finally {
lock.unlock();
}
}
错误 4:混淆 LTS 和非 LTS 版本
// 错误认知:认为所有 Java 25 特性都稳定
// Java 25 是非 LTS 版本,部分特性仍处于预览状态
// 正确做法:评估预览特性
// 1. 预览特性可能在后续版本中改变
// 2. 生产环境谨慎使用预览特性
// 3. 关注官方文档和社区反馈
练习题
练习 1:记录模式应用
题目:
使用记录模式重构以下代码,使其更简洁:
record Person(String name, int age) {}
record Employee(Person person, String department, double salary) {}
public static void printEmployeeInfo(Object obj) {
if (obj instanceof Employee) {
Employee e = (Employee) obj;
Person p = e.person();
System.out.printf("员工: %s, 年龄: %d, 部门: %s, 薪资: %.2f%n",
p.name(), p.age(), e.department(), e.salary());
}
}
答案:
public static void printEmployeeInfo(Object obj) {
if (obj instanceof Employee(
Person(String name, int age),
String department,
double salary
)) {
System.out.printf("员工: %s, 年龄: %d, 部门: %s, 薪资: %.2f%n",
name, age, department, salary);
}
}
练习 2:字符串模板
题目:
使用字符串模板重写以下代码,生成一个 HTML 表格:
String name = "张三";
String email = "zhangsan@example.com";
String role = "管理员";
String html = "<table>"
+ "<tr><td>姓名</td><td>" + name + "</td></tr>"
+ "<tr><td>邮箱</td><td>" + email + "</td></tr>"
+ "<tr><td>角色</td><td>" + role + "</td></tr>"
+ "</table>";
答案:
String html = STR."""
<table>
<tr><td>姓名</td><td>\{name}</td></tr>
<tr><td>邮箱</td><td>\{email}</td></tr>
<tr><td>角色</td><td>\{role}</td></tr>
</table>
""";
练习 3:虚拟线程任务
题目:
使用虚拟线程并发执行 10 个任务,每个任务随机休眠 100-500ms,计算总耗时。
答案:
import java.util.concurrent.Executors;
public class ConcurrencyExercise {
public static void main(String[] args) throws InterruptedException {
int taskCount = 10;
long start = System.currentTimeMillis();
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < taskCount; i++) {
final int taskId = i;
executor.submit(() -> {
try {
long sleepTime = (long) (Math.random() * 400 + 100);
Thread.sleep(sleepTime);
System.out.printf("任务 %d 完成,耗时 %d ms%n",
taskId + 1, sleepTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
long end = System.currentTimeMillis();
System.out.printf("\n总耗时: %d ms%n", end - start);
}
}
总结
本章全面介绍了 Java 25 的主要新特性:
核心要点回顾
- 语言层面改进
- 并发和性能改进
- API 和标准库增强
- JVM 和垃圾回收改进
- 开发者体验提升
学习建议
- 循序渐进:不要一次性掌握所有特性,选择最常用的深入学习
- 实践为主:多写代码,理解每个特性的应用场景
- 关注预览特性:预览特性可能会改变,谨慎在生产环境使用
- 参考官方文档:Oracle 的官方文档是最权威的信息来源
版本升级建议
升级路径:
- 从 Java 17/21 升级到 Java 25 相对容易
- 从 Java 8 或 11 升级需要更多测试
- 使用
jdeprscan工具检查过期的 API
下章预告
下一章我们将学习:第 03 章 JDK安装与开发环境配置
在这一章中,你将学到:
- 如何下载和安装 JDK 25
- 如何配置环境变量(JAVA_HOME、PATH)
- 如何选择合适的 JDK 发行版(Oracle、OpenJDK、Adoptium 等)
- 如何配置 IDE(IntelliJ IDEA、Eclipse、VS Code)
- 如何验证 JDK 安装是否成功
- 如何管理多个 JDK 版本
准备好开始你的 Java 开发之旅了吗?下一章见!
