从JDK 8到JDK 21:升级之路与新特性详解
前言
作为一名资深Java开发工程师,我们的项目最近完成了从JDK 8到JDK 21的重大版本升级。这次升级不仅带来了显著的性能提升,更重要的是引入了许多令人兴奋的新特性,极大地提升了开发效率和代码质量。本文将详细介绍这次升级过程中遇到的关键新特性,并提供实际的代码示例和升级注意事项。
JDK 21的重要地位
JDK 21是继JDK 8、JDK 11、JDK 17之后的第四个长期支持(LTS)版本,发布于2023年9月。从JDK 8跨越到JDK 21,我们将体验到Java语言和平台13年来的重大进化。
主要新特性详解
1. 文本块(Text Blocks)- JDK 15引入
文本块极大地简化了多行字符串的处理,特别适用于SQL语句、JSON、HTML等场景。
传统写法:
// JDK 8中的多行字符串处理
String sql = "SELECT user_id, username, email \n" +
"FROM users \n" +
"WHERE status = 'ACTIVE' \n" +
" AND created_date > '2023-01-01' \n" +
"ORDER BY username";
String json = "{\n" +
" \"name\": \"张三\",\n" +
" \"age\": 30,\n" +
" \"email\": \"zhangsan@example.com\"\n" +
"}";
JDK 21中的文本块:
// 使用文本块,代码更加清晰和易维护
String sql = """
SELECT user_id, username, email
FROM users
WHERE status = 'ACTIVE'
AND created_date > '2023-01-01'
ORDER BY username
""";
String json = """
{
"name": "张三",
"age": 30,
"email": "zhangsan@example.com"
}
""";
2. Switch表达式 - JDK 14标准化
Switch表达式提供了更简洁和安全的分支处理方式,支持模式匹配和返回值。
传统Switch语句:
// JDK 8中的传统switch语句
public String getOrderStatus(int statusCode) {
String status;
switch (statusCode) {
case 1:
status = "待支付";
break;
case 2:
status = "已支付";
break;
case 3:
status = "已发货";
break;
case 4:
status = "已完成";
break;
default:
status = "未知状态";
break;
}
return status;
}
JDK 21中的Switch表达式:
// 使用switch表达式,更加简洁
public String getOrderStatus(int statusCode) {
return switch (statusCode) {
case 1 -> "待支付";
case 2 -> "已支付";
case 3 -> "已发货";
case 4 -> "已完成";
default -> "未知状态";
};
}
// 支持复杂逻辑的switch表达式
public double calculateDiscount(String memberType, double amount) {
return switch (memberType) {
case "VIP" -> {
if (amount > 1000) {
yield amount * 0.8; // 20% 折扣
} else {
yield amount * 0.9; // 10% 折扣
}
}
case "GOLD" -> amount * 0.85; // 15% 折扣
case "SILVER" -> amount * 0.95; // 5% 折扣
default -> amount; // 无折扣
};
}
3. 记录类(Records)- JDK 16引入
Records提供了一种简洁的方式来创建不可变数据载体类,自动生成构造函数、getter方法、equals、hashCode和toString方法。
传统的数据类:
// JDK 8中需要大量样板代码的数据类
public class User {
private final String name;
private final int age;
private final String email;
public User(String name, int age, String email) {
this.name = name;
this.age = age;
this.email = email;
}
public String getName() { return name; }
public int getAge() { return age; }
public String getEmail() { return email; }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
User user = (User) obj;
return age == user.age &&
Objects.equals(name, user.name) &&
Objects.equals(email, user.email);
}
@Override
public int hashCode() {
return Objects.hash(name, age, email);
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + ", email='" + email + "'}";
}
}
JDK 21中的Record:
// 使用Record,一行代码搞定
public record User(String name, int age, String email) {
// 可以添加自定义构造函数进行验证
public User {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
if (email == null || !email.contains("@")) {
throw new IllegalArgumentException("邮箱格式不正确");
}
}
// 可以添加自定义方法
public boolean isAdult() {
return age >= 18;
}
}
// 使用示例
User user = new User("张三", 25, "zhangsan@example.com");
System.out.println(user.name()); // 张三
System.out.println(user.isAdult()); // true
System.out.println(user); // User[name=张三, age=25, email=zhangsan@example.com]
4. 密封类(Sealed Classes)- JDK 17引入
密封类允许你控制哪些类可以继承或实现一个类或接口,提供了更好的类型安全性。
// 定义密封类
public sealed abstract class Shape
permits Circle, Rectangle, Triangle {
public abstract double area();
}
// 允许的子类
public final class Circle extends Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public final class Rectangle extends Shape {
private final double width, height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
public double area() {
return width * height;
}
}
public non-sealed class Triangle extends Shape {
private final double base, height;
public Triangle(double base, double height) {
this.base = base;
this.height = height;
}
@Override
public double area() {
return 0.5 * base * height;
}
}
// 使用密封类进行模式匹配
public class ShapeProcessor {
public String processShape(Shape shape) {
return switch (shape) {
case Circle c -> "圆形,面积:" + c.area();
case Rectangle r -> "矩形,面积:" + r.area();
case Triangle t -> "三角形,面积:" + t.area();
// 由于是密封类,编译器知道所有可能的类型,不需要default分支
};
}
}
5. 模式匹配(Pattern Matching)
instanceof的模式匹配 - JDK 16引入
// JDK 8中的传统写法
public void processObject(Object obj) {
if (obj instanceof String) {
String str = (String) obj;
System.out.println("字符串长度:" + str.length());
} else if (obj instanceof Integer) {
Integer num = (Integer) obj;
System.out.println("整数值:" + num);
} else if (obj instanceof List) {
List<?> list = (List<?>) obj;
System.out.println("列表大小:" + list.size());
}
}
// JDK 21中的模式匹配
public void processObject(Object obj) {
if (obj instanceof String str) {
System.out.println("字符串长度:" + str.length());
} else if (obj instanceof Integer num) {
System.out.println("整数值:" + num);
} else if (obj instanceof List<?> list) {
System.out.println("列表大小:" + list.size());
}
}
Switch中的模式匹配(预览功能)
public String analyzeValue(Object value) {
return switch (value) {
case String s -> "字符串:" + s;
case Integer i when i > 0 -> "正整数:" + i;
case Integer i when i < 0 -> "负整数:" + i;
case Integer i -> "零";
case List<?> list when list.isEmpty() -> "空列表";
case List<?> list -> "列表,大小:" + list.size();
case null -> "空值";
default -> "未知类型:" + value.getClass().getSimpleName();
};
}
6. 虚拟线程(Virtual Threads)- JDK 21引入
虚拟线程是JDK 21最重要的新特性之一,它极大地简化了并发编程,特别适用于I/O密集型应用。
传统线程模型的问题:
// JDK 8中的传统线程处理大量并发请求
public class TraditionalThreadServer {
private final ExecutorService executor = Executors.newFixedThreadPool(100);
public void handleRequests() {
for (int i = 0; i < 10000; i++) {
final int requestId = i;
executor.submit(() -> {
try {
// 模拟I/O操作
Thread.sleep(1000);
System.out.println("处理请求:" + requestId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
}
JDK 21中的虚拟线程:
// 使用虚拟线程处理大量并发请求
public class VirtualThreadServer {
public void handleRequests() {
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 10000; i++) {
final int requestId = i;
executor.submit(() -> {
try {
// 模拟I/O操作
Thread.sleep(1000);
System.out.println("处理请求:" + requestId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
}
}
// 直接创建虚拟线程
public void createVirtualThread() {
Thread virtualThread = Thread.ofVirtual()
.name("virtual-worker")
.start(() -> {
try {
Thread.sleep(2000);
System.out.println("虚拟线程执行完成:" + Thread.currentThread());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 使用Structured Concurrency(结构化并发)
public String fetchUserData(int userId) throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> userTask = scope.fork(() -> fetchUserProfile(userId));
Supplier<String> orderTask = scope.fork(() -> fetchUserOrders(userId));
Supplier<String> preferencesTask = scope.fork(() -> fetchUserPreferences(userId));
scope.join(); // 等待所有任务完成
scope.throwIfFailed(); // 如果有任务失败则抛出异常
return combineUserData(userTask.get(), orderTask.get(), preferencesTask.get());
}
}
private String fetchUserProfile(int userId) {
// 模拟数据库查询
return "用户资料:" + userId;
}
private String fetchUserOrders(int userId) {
// 模拟订单查询
return "用户订单:" + userId;
}
private String fetchUserPreferences(int userId) {
// 模拟偏好设置查询
return "用户偏好:" + userId;
}
private String combineUserData(String profile, String orders, String preferences) {
return String.format("用户数据 [%s, %s, %s]", profile, orders, preferences);
}
}
7. 改进的NullPointerException信息
JDK 21提供了更详细的空指针异常信息,帮助快速定位问题。
public class User {
private String name;
private Address address;
// getter和setter方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
}
public class Address {
private String street;
private String city;
public String getStreet() { return street; }
public void setStreet(String street) { this.street = street; }
public String getCity() { return city; }
public void setCity(String city) { this.city = city; }
}
public class NPEExample {
public static void main(String[] args) {
User user = new User();
user.setName("张三");
// address为null
// JDK 8中的异常信息:
// Exception in thread "main" java.lang.NullPointerException
// at NPEExample.main(NPEExample.java:XX)
// JDK 21中的详细异常信息:
// Exception in thread "main" java.lang.NullPointerException:
// Cannot invoke "Address.getCity()" because the return value of "User.getAddress()" is null
String city = user.getAddress().getCity();
}
}
8. 增强的Stream API
JDK 21对Stream API进行了多项改进,提供了更多便利的操作方法。
import java.util.*;
import java.util.stream.*;
public class StreamEnhancements {
public static void main(String[] args) {
List<String> items = List.of("apple", "banana", "cherry", "date", "elderberry");
// takeWhile 和 dropWhile(JDK 9引入)
List<String> taken = items.stream()
.takeWhile(s -> s.length() <= 6)
.collect(Collectors.toList());
System.out.println("取到长度<=6的元素:" + taken); // [apple, banana, cherry]
List<String> dropped = items.stream()
.dropWhile(s -> s.length() <= 6)
.collect(Collectors.toList());
System.out.println("丢弃长度<=6的元素后:" + dropped); // [elderberry]
// Stream.of() 支持更多类型
Stream.of(1, 2, 3, 4, 5)
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.forEach(System.out::println); // 4, 16
// 使用新的收集器
Map<Integer, List<String>> groupedByLength = items.stream()
.collect(Collectors.groupingBy(String::length));
System.out.println("按长度分组:" + groupedByLength);
// teeing收集器(JDK 12引入)
String summary = items.stream()
.collect(Collectors.teeing(
Collectors.counting(),
Collectors.mapping(String::length, Collectors.summingInt(Integer::intValue)),
(count, totalLength) -> String.format("共%d个元素,总长度%d", count, totalLength)
));
System.out.println(summary);
}
}
9. HTTP客户端API增强
JDK 21的HTTP客户端提供了更现代和灵活的HTTP通信方式。
import java.net.http.*;
import java.net.URI;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class HttpClientExample {
public static void main(String[] args) throws Exception {
// 创建HTTP客户端
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(10))
.followRedirects(HttpClient.Redirect.NORMAL)
.build();
// 同步GET请求
HttpRequest getRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.timeout(Duration.ofSeconds(30))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> getResponse = client.send(getRequest,
HttpResponse.BodyHandlers.ofString());
System.out.println("状态码:" + getResponse.statusCode());
System.out.println("响应体:" + getResponse.body());
// 异步POST请求
String jsonBody = """
{
"name": "张三",
"email": "zhangsan@example.com"
}
""";
HttpRequest postRequest = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.timeout(Duration.ofSeconds(30))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
CompletableFuture<HttpResponse<String>> futureResponse =
client.sendAsync(postRequest, HttpResponse.BodyHandlers.ofString());
futureResponse.thenAccept(response -> {
System.out.println("异步响应状态码:" + response.statusCode());
System.out.println("异步响应体:" + response.body());
}).join();
}
}
JDK 8升级到JDK 21的注意事项
1. 模块系统兼容性
JDK 9引入的模块系统可能影响某些库的使用:
// 如果使用模块系统,需要在module-info.java中声明依赖
module com.example.myapp {
requires java.base;
requires java.logging;
requires java.net.http;
exports com.example.myapp.api;
}
2. 移除的API和工具
一些在JDK 8中可用的API和工具在JDK 21中已被移除:
- Nashorn JavaScript引擎:JDK 15中移除,需要使用GraalVM或Rhino替代
- Applet API:JDK 17中移除
- Security Manager:JDK 21中标记为移除
// 不再可用的Nashorn引擎
// ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
// 替代方案:使用GraalVM的JavaScript引擎
// 或者迁移到服务端JavaScript解决方案
3. 第三方库兼容性
确保所有第三方依赖库都支持JDK 21:
<!-- Maven依赖检查示例 -->
<dependencies>
<!-- 确保Spring Boot版本支持JDK 21 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 检查Hibernate版本 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.0.Final</version>
</dependency>
</dependencies>
4. 编译和运行时参数调整
# 编译时可能需要的参数
javac --enable-preview --source 21 --target 21 *.java
# 运行时可能需要的参数
java --enable-preview -XX:+UseZGC -XX:+UnlockExperimentalVMOptions MyApp
# 如果遇到模块访问问题
java --add-opens java.base/java.lang=ALL-UNNAMED MyApp
5. 性能调优建议
// JVM参数优化示例
// -XX:+UseZGC: 使用ZGC垃圾收集器
// -XX:+UseG1GC: 使用G1垃圾收集器(推荐)
// -Xmx4g: 设置最大堆内存
// -XX:+EnableDynamicAgentLoading: 允许动态代理加载
// 虚拟线程相关参数
// -Djdk.virtualThreadScheduler.parallelism=CPU核心数
// -Djdk.virtualThreadScheduler.maxPoolSize=256
6. 单元测试适配
// JUnit 5适配JDK 21
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledOnJre;
import org.junit.jupiter.api.condition.JRE;
public class JDK21FeatureTest {
@Test
@EnabledOnJre(JRE.JAVA_21)
void testVirtualThreads() {
// 测试虚拟线程功能
Thread virtualThread = Thread.ofVirtual()
.name("test-virtual-thread")
.start(() -> {
System.out.println("虚拟线程测试");
});
assertThat(virtualThread.isVirtual()).isTrue();
}
@Test
void testRecords() {
// 测试Record类
record TestRecord(String name, int value) {}
TestRecord record = new TestRecord("test", 42);
assertThat(record.name()).isEqualTo("test");
assertThat(record.value()).isEqualTo(42);
}
}
性能改进和优化
1. 启动时间和内存使用
JDK 21相比JDK 8在启动时间和内存使用方面有显著改进:
// 应用启动性能测试
public class PerformanceTest {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
// 应用启动逻辑
initializeApplication();
long endTime = System.currentTimeMillis();
System.out.println("应用启动时间:" + (endTime - startTime) + "ms");
// 内存使用情况
Runtime runtime = Runtime.getRuntime();
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
System.out.println("内存使用:" + (usedMemory / 1024 / 1024) + "MB");
}
private static void initializeApplication() {
// 模拟应用初始化
}
}
2. 垃圾收集器改进
// G1GC和ZGC性能比较
public class GCPerformanceTest {
public static void main(String[] args) {
List<byte[]> memoryConsumer = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
// 创建大量对象测试GC性能
memoryConsumer.add(new byte[1024 * 1024]); // 1MB
if (i % 1000 == 0) {
System.gc(); // 手动触发GC进行测试
System.out.println("已分配对象数量:" + i);
}
}
}
}
总结
从JDK 8升级到JDK 21是一次重大的技术升级,带来了众多激动人心的新特性和改进:
开发效率提升
- 文本块简化了多行字符串处理,代码更加清晰
- Records大幅减少了样板代码,提高了数据类的开发效率
- Switch表达式提供了更简洁和安全的分支处理方式
- 模式匹配让类型检查和转换更加直观
并发编程革命
- 虚拟线程是最重要的新特性,极大地简化了高并发应用的开发
- 结构化并发提供了更好的并发任务管理方式
- 传统的线程池模式在I/O密集型场景下不再是必需品
类型安全增强
- 密封类提供了更严格的继承控制
- 模式匹配结合密封类实现了更安全的类型处理
- 编译器的类型推断能力显著增强
性能和稳定性改进
- 启动时间和内存使用都有显著优化
- 垃圾收集器性能持续改进
- JVM的整体稳定性和性能都有提升
现代化的API
- HTTP客户端提供了现代化的HTTP通信方式
- Stream API持续增强,提供更多便利操作
- 错误信息更加详细和友好
升级建议
- 逐步迁移:建议先在测试环境进行充分测试,确保所有依赖库兼容
- 性能测试:重点测试应用的启动时间、内存使用和并发性能
- 代码重构:逐步采用新特性重构现有代码,提升代码质量
- 团队培训:确保团队成员熟悉新特性,特别是虚拟线程的使用
JDK 21作为最新的LTS版本,不仅向后兼容性良好,更为Java的未来发展奠定了坚实基础。对于企业级应用来说,这次升级是一次重要的技术投资,将在未来几年内持续带来价值。
虽然升级过程中需要注意一些兼容性问题,但收益远大于成本。特别是虚拟线程的引入,为Java在云原生和微服务架构中的应用开辟了新的可能性。
现在是拥抱JDK 21的最佳时机,让我们的Java应用在新时代中焕发新的活力!