后端 个人总结 Java 8 至 Java 21 的核心特性梳理 Jint 2026-02-28 2026-03-06
为了满足线上安全审计的要求,公司很久前就完成从 JDK 8 到 JDK 21 的迁移。尽管生产环境已经‘先行一步’,但在实际开发中,我发现自己对新特性的运用仍处于‘原地踏步’的状态。趁着今日复盘,我决定梳理下从 JDK8 升级到 JDK 21 后,有哪些好用的新特性.
语法特性 局部变量类型推断 通过 var 关键字减少繁琐的类型声明(JDK 10)
JDK 8 (旧写法):
1 2 3 4 5 6 7 Map<String, List<UserDTO>> userMap = new HashMap <String, List<UserDTO>>(); for (Map.Entry<String, List<UserDTO>> entry : userMap.entrySet()) { List<UserDTO> users = entry.getValue(); }
JDK 21 (新写法):
1 2 3 4 5 6 7 var userMap = new HashMap <String, List<UserDTO>>();for (var entry : userMap.entrySet()) { var users = entry.getValue(); }
文本块 使用 """ 编写多行字符串,再也不用手动拼 \n 和 + 号了(JDK 15)
JDK 8 (旧写法):
1 2 3 4 5 6 String json = "{\n" + " " name": " jdk",\n" + " " version": " 21 ",\n" + " " status": " LTS"\n" + "}" ;
JDK 21 (新写法):
1 2 3 4 5 6 7 8 String json = """ { "name": "jdk", "version": "21", "status": "LTS" } """ ;
未命名模式与变量 参照python等语言,用下划线 _ 代替不再使用的变量,显著提升代码的可读性,减少静态检查警告。
1 2 3 4 5 try { int number = Integer.parseInt(input); } catch (NumberFormatException _) { System.out.println("输入格式有误,请输入数字。" ); }
1 2 3 for (int _ = 0 ; _ < 5 ; _++) { System.out.println("正在初始化..." ); }
instanceof 模式匹配 直接在 instanceof 判断后定义变量,无需强转。
JDK 8 (旧写法):
1 2 3 4 if (obj instanceof String) { String s = (String) obj; System.out.println(s.length()); }
JDK 21 (新写法):
1 2 3 4 if (obj instanceof String s) { System.out.println(s.length()); }
Switch 表达式与模式匹配 switch 已经从单纯的控制流语句进化成了表达式 ,并全面支持数据类型的模式匹配 (JDK 14/21)。
Switch 作为表达式
1 2 3 4 5 6 7 String day = "MONDAY" ;int numLetters = switch (day) { case "MONDAY" , "FRIDAY" , "SUNDAY" -> 6 ; case "TUESDAY" -> 7 ; case null -> -1 ; default -> 0 ; };
类型模式匹配
1 2 3 4 5 6 7 8 9 public static String formatterPattern (Object obj) { return switch (obj) { case Integer i -> String.format("Integer: %d" , i); case Long l -> String.format("Long: %d" , l); case Double d -> String.format("Double: %f" , d); case String s -> String.format("String: %s" , s); default -> obj.toString(); }; }
守卫模式
在 case 标签后紧跟 when 表达式,只有当类型匹配 且 when 后面的布尔表达式为 true 时,该分支才会执行。
1 2 3 4 5 6 7 Object obj = 100 ;switch (obj) { case Integer i when i > 0 -> System.out.println("正数" ); case Integer i when i < 0 -> System.out.println("负数" ); case Integer i -> System.out.println("这是零" ); default -> System.out.println("不是整数" ); }
注意区别
**-> (箭头)**:代表“只选其一”,执行完即刻结束。
**: (冒号)**:代表“从这里开始”,如果不写 break 就会一直往下走。
Record类型 自动生成构造函数、Getter、equals、hashCode 和 toString (JDK 16)
JDK 8 (旧写法):
1 2 3 4 5 6 7 8 9 @Getter @AllArgsConstructor @EqualsAndHashCode @ToString public final class UserDTO { private final String name; private final Integer age; }
JDK 21 (新写法):
1 2 3 4 5 6 7 8 9 10 public record UserDTO (String name, Integer age) { public UserDTO withAge (Integer newAge) { return new UserDTO (this .name, newAge); } }
Record类型 的解构 在 instanceof 或 switch 中直接对 Record 进行解构拆包(JDK 21)。
JDK 8 (旧写法):
1 2 3 4 5 if (obj instanceof Point p) { int x = p.x(); int y = p.y(); System.out.println("X: " + x + ", Y: " + y); }
JDK 21 (新写法):
instanceof 解构示例:
1 2 3 4 5 6 7 record Point (int x, int y) {}public void print (Object obj) { if (obj instanceof Point (int x, int y) ) { System.out.println("X: " + x + ", Y: " + y); } }
switch 解构示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 record Point (double x, double y) {}record Window (Point topLeft, Point bottomRight) public void checkWindow (Object obj) { switch (obj) { case Window (Point(double x1, double y1) , Point(double x2, double y2)) when x1 == x2 -> System.out.println("这是一个垂直窗口" ); case Window w -> System.out.println("普通窗口对象: " + w); default -> System.out.println("不是窗口" ); } }
密封类 使用 sealed 和 permits 关键字精确控制哪些类可以继承当前类(JDK 17)
简单来说,在密封类出现之前,Java 的类继承只有两个极端:要么完全开放(默认),要么完全禁死(final)。密封类提供了一种“中间态”:我允许别人继承我,但必须是我指定的“亲儿子”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public sealed class Light permits RedLight, GreenLight, YellowLight {}public final class RedLight extends Light {}public non-sealed class GreenLight extends Light {}public sealed class YellowLight extends Light permits FlashingYellow {}final class FlashingYellow extends YellowLight {}
同时,密封类的子类不能“含糊其辞”,它们必须在三个修饰符中选择其一,以明确继承链的未来:
**final**:禁止进一步继承。继承链到此为止。
**sealed**:继续开启密封模式。它本身也是密封类,需要再次使用 permits 指定它的子类。
**non-sealed**:打破密封限制,允许任何类继承它。这是 Java 为了保持灵活性留出的出口。
核心库增强 String 增强方法 isBlank(), lines(), strip(), repeat(n)。这些方法让很多原本依赖 StringUtils 的代码变得原生化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 String input = " " ;boolean isBlank = input.isBlank(); String name = " Java 21 " ; String cleanName = name.strip(); String star = "*" .repeat(5 ); String logs = "Error1\nError2" ;long count = logs.lines().count();
集合工厂方法 List.of(), Set.of(), Map.of() 快速创建不可变集合(JDK 9)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 List<String> list = List.of("Java" , "Python" , "Go" ); Set<Integer> set = Set.of(1 , 2 , 3 ); Map<String, Integer> map = Map.of("Apple" , 10 , "Banana" , 20 ); Map<String, Integer> bigMap = Map.ofEntries( Map.entry("A" , 1 ), Map.entry("B" , 2 ) );
Sequenced Collections (序列集合) 新增 SequencedCollection 接口,统一了获取集合首尾元素的操作(JDK 21)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 List<String> list = new ArrayList <>(List.of("A" , "B" , "C" )); LinkedHashSet<String> set = new LinkedHashSet <>(List.of("X" , "Y" , "Z" )); System.out.println(list.getFirst()); System.out.println(set.getLast()); System.out.println(list.reversed()); list.addFirst("Start" ); list.addLast("End" ); list.removeFirst(); list.removeLast();
新的 HttpClient 原生的 HTTP/2 客户端(JDK 11),支持异步和 WebSocket,相比老旧的 HttpURLConnection 好用太多了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 HttpClient client = HttpClient.newBuilder() .connectTimeout(Duration.ofSeconds(5 )) .build(); HttpRequest request = HttpRequest.newBuilder() .uri(URI.create("https://httpbin.org/get" )) .header("Accept" , "application/json" ) .GET() .build(); HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println("状态码: " + response.statusCode()); System.out.println("响应体: " + response.body());
1 2 3 4 5 client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenAccept(System.out::println) .join();
Stream API 增强 增加了toList() ,takeWhile, dropWhile等更实用的方法。
1 2 3 4 5 6 List<String> list = List.of("Apple" , "Banana" , "Cherry" ); List<String> result = list.stream() .filter(s -> s.startsWith("A" )) .toList();
截断流
1 2 3 4 5 6 7 8 9 10 11 12 13 List<Integer> numbers = List.of(1 , 2 , 3 , 10 , 4 , 5 ); List<Integer> taken = numbers.stream() .takeWhile(n -> n < 10 ) .toList(); List<Integer> dropped = numbers.stream() .dropWhile(n -> n < 10 ) .toList();
Optional 增强 增加了 ifPresentOrElse, or, stream 等方法。
1 2 3 4 5 6 7 8 Optional<String> username = Optional.ofNullable(getUser()); username.ifPresentOrElse( name -> System.out.println("欢迎你:" + name), () -> System.out.println("请先登录" ) );
1 2 3 Optional<String> config = getCacheConfig() .or(() -> getDatabaseConfig()) .or(() -> Optional.of("DefaultConfig" ));
1 2 3 4 5 6 7 8 List<String> userIds = List.of("1" , "2" , "3" ); List<User> users = userIds.stream() .map(id -> findUserById(id)) .flatMap(Optional::stream) .toList();
并发与性能 虚拟线程 JDK 21 最核心的特性。 作为一种轻量级线程,它允许在普通硬件环境下轻松创建数百万个实例,显著提升了阻塞式编程(如基于 Servlet 架构的任务)的并发处理能力。由于虚拟线程本质上是在少量平台线程上进行多路复用 ,它极大地优化了 I/O 密集型 场景的资源利用率,但并不适用于 CPU 密集型 任务。
直接创建虚拟线程
1 2 3 Thread.startVirtualThread(() -> { System.out.println("你好,我是虚拟线程: " + Thread.currentThread()); });
使用 ExecutorService (推荐):
1 2 3 4 5 6 try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { executor.submit(() -> { System.out.println("正在处理并发任务..." ); }); }
ZGC垃圾回收器 ZGC 专为极低延迟设计。在 JDK 21 中,它正式支持了分代收集,能够更频繁地回收短命对象。无论内存是 8MB 还是 16TB,停顿时间均能控制在 1ms 以内,基本消灭了“Stop The World”带来的卡顿感。
结合使用 Record类 + 参数校验 利用 Record 的 紧凑构造函数 ,在构造对象时实现极简的参数校验
1 2 3 4 5 6 7 8 9 10 11 public record GeoLocation (double longitude, double latitude) { public GeoLocation { if (longitude < -180 || longitude > 180 ) { throw new IllegalArgumentException ("经度范围不正确" ); } if (latitude < -90 || latitude > 90 ) { throw new IllegalArgumentException ("纬度范围不正确" ); } } }
Record结合Optional + Stream + Switch 优雅的处理数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public record Order (String id, double amount, String category) {}public class DiscountService { public double getDiscountedAmount (Optional<Order> orderOpt) { return orderOpt .stream() .filter(order -> order.amount() > 0 ) .map(order -> switch (order) { case Order (var id, var amt, var cat) when cat.equals("ELECTRONICS" ) -> amt * 0.9 ; case Order (var id, var amt, var cat) when cat.equals("BOOKS" ) -> amt * 0.8 ; case Order (var id, var amt, var cat) -> amt; }) .findFirst().orElse(0.0 ); } }
密封类结合模式匹配 这是密封类最强大的地方。在 Java 17+ 的 switch 表达式中,如果你对密封类进行模式匹配,编译器可以自动检查你是否覆盖了所有可能的情况 。
1 2 3 4 5 6 7 8 9 10 11 public sealed interface Shape permits Circle, Square {}final class Circle implements Shape { double radius; }final class Square implements Square { double side; }double area = switch (shape) { case Circle c -> Math.PI * c.radius * c.radius; case Square s -> s.side * s.side; };
设计模式新写法 策略模式 传统写法 :定义一个接口,然后写一大堆实现类(如 CreditCardStrategy, WeChatPayStrategy),最后可能还要用简单工厂来创建。 JDK 21 写法 :使用 Sealed Interface + Record + Switch 模式匹配 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public sealed interface PayStrategy { record CreditCard (String cardNumber) implements PayStrategy {} record WeChatPay (String openId) implements PayStrategy {} record Cash () implements PayStrategy {} } public void processPayment (PayStrategy strategy, double amount) { switch (strategy) { case PayStrategy.CreditCard(var code) -> System.out.println("刷卡:" + code); case PayStrategy.WeChatPay(var id) -> System.out.println("微信支付:" + id); case PayStrategy.Cash() -> System.out.println("现金支付" ); } }
逻辑不再分散在各个子类中,而是集中在业务处理器里。这对于逻辑不复杂、策略相对固定的场景极其高效。
1 2 3 4 5 6 7 8 service.processPayment(new PayStrategy .CreditCard("6222-xxxx-xxxx-0001" ), 100.0 ); service.processPayment(new PayStrategy .WeChatPay("wx_user_9527" ), 50.5 ); service.processPayment(new PayStrategy .Cash(), 20.0 );
建造者模式 传统写法 :手写或使用 Lombok 的 @Builder,创建一个内部静态类来逐步构建对象。
JDK 21 写法 :Wither 模式 + 链式构造 。
1 2 3 4 var user = new UserDTO ("张三" , 18 ) .withAge(19 ) .withEmail("test@test.com" );
状态模式 传统写法 :每个状态都是一个子类,状态切换逻辑分散在各个子类的方法中。
JDK 21 写法 :Sealed Classes + 类型覆盖检查。
1 2 3 4 5 6 7 8 9 10 11 12 13 sealed interface ConnectionState {}record Disconnected () implements ConnectionState {}record Connecting (int attempts) implements ConnectionState {}record Connected (String sessionId) implements ConnectionState {}public ConnectionState next (ConnectionState current) { return switch (current) { case Disconnected () -> new Connecting (1 ); case Connecting (int a) when a < 3 -> new Connecting (a + 1 ); case Connecting (int a) -> new Disconnected (); case Connected (String id) -> current; }; }
利用 switch 对密封类的强校验。如果你增加了一个新的 Record 状态而没有在 switch 中处理,编译器会直接报错。这比传统状态模式更难出错。
1 2 3 4 ConnectionState state = new Disconnected ();for (int i = 0 ; i < 5 ; i++) { state = next(state); }
责任链模式 结合 Optional 和 switch 的新增强,责任链可以写得非常扁平化,不再需要层层嵌套。
1 2 3 4 5 6 7 8 9 10 11 record LogRequest (String message, String level) {}record AuthRequest (String username, String password) {}public void handleRequest (Object request) { switch (request) { case LogRequest (String msg, var level) -> System.out.println("Log: " + msg); case AuthRequest (var user, var password) -> authenticate(user, password); case null -> throw new IllegalArgumentException ("Request cannot be null" ); default -> System.out.println("Unknown request" ); } }
装饰器模式 Record类的不可变性和简洁性使得创建包装类更加轻量。
1 2 3 4 5 6 7 8 9 10 11 12 interface Renderer { String render (String text) ; }record PlainRenderer () implements Renderer { public String render (String text) { return text; } } record BoldRenderer (Renderer inner) implements Renderer { public String render (String text) { return "<b>" + inner.render(text) + "</b>" ; } }
个人使用总结
引入 Record 类简化 POJO 样板代码.
增强 Switch 表达式与模式匹配,大幅简化了分支判断的代码.
融入现代语言语法:引入文本块 (Text Blocks)、局部变量推断 (var) 等特性,好像借鉴了python的写法.
引入虚拟线程 , 虽在表现上对标 Python 协程,但通过“阻塞即挂起”实现了类似 Netty EventLoop 的非阻塞高吞吐。
垃圾回收器越来越高级了, 程序员顶多改改最大堆内存 -Xmx 就行了.