Java 21于2023年9月正式发布,作为继Java 17之后的新一代LTS(长期支持)版本,这个版本不是"小修小补",而是一次并发模型的彻底革命语言表达力的跨越式提升。本版本包含15个JEP特性,其中虚拟线程、记录模式、模式匹配等核心特性从Preview正式转正,标志着Java在现代化道路上迈出了关键一步。

核心特性概览

Java 21引入的主要技术特性包括:

  • 虚拟线程 (Virtual Threads) - JEP 444 [正式发布]
  • 记录模式 (Record Patterns) - JEP 440 [正式发布]
  • Switch模式匹配 - JEP 441 [正式发布]
  • 顺序集合 (Sequenced Collections) - JEP 431 [正式发布]
  • 字符串模板 (String Templates) - JEP 430 [Preview]
  • 结构化并发 (Structured Concurrency) - JEP 453 [Preview]
  • 作用域值 (Scoped Values) - JEP 446 [Preview]

1. 虚拟线程 (Virtual Threads) - JEP 444:告别线程池地狱

解决的核心痛点

传统Java并发编程就是对于资源的管理要非常小心!

想处理10万个并发请求?传统做法是创建线程池,然后开始纠结:线程池设多大?100?1000?设小了吞吐量不够,设大了内存直接爆炸(每个平台线程1-2MB栈内存)!更别提那些复杂的线程池参数配置了:corePoolSize、maximumPoolSize、keepAliveTime、队列类型...一个配置不当就是线上事故。

更恶心的是异步回调地狱:为了不阻塞珍贵的线程资源,你被迫使用CompletableFuture,代码变成了嵌套的意大利面条,debug时想死的心都有。

技术革新

虚拟线程彻底改变游戏规则!它是JVM管理的超轻量级线程,采用M:N调度模型:数百万个虚拟线程可以映射到少数几个操作系统线程上。每个虚拟线程只占用几KB内存,而且当遇到I/O阻塞时会自动"挂起",让出底层的载体线程去执行其他虚拟线程。

最爽的地方:你可以回归同步编程模型!不需要异步回调,不需要响应式编程,写出来的代码就像单线程一样简单清晰,但性能却是传统线程池的数十倍甚至上百倍。

// 传统方式 - 线程池地狱
ExecutorService executor = new ThreadPoolExecutor(
    5020060L, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(1000),
    new ThreadPoolExecutor.CallerRunsPolicy()
);
// 处理10万并发?线程池参数怎么设置?设小了不够用,设大了内存爆炸!

// Java 21方式 - 虚拟线程
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 1_000_000; i++) {  // 100万并发?小意思!
        executor.submit(() -> {
            Thread.sleep(1000);  // I/O阻塞时自动挂起
            // 处理业务逻辑...
        });
    }
} // 自动关闭,等待所有任务完成

// 创建虚拟线程的几种方式
Thread vThread = Thread.ofVirtual().start(() -> {
    System.out.println("我是虚拟线程:" + Thread.currentThread().isVirtual());
});
vThread.join();

值不值得学?

必学!这是Java并发编程的未来!

2. 顺序集合 (Sequenced Collections) - JEP 431:集合框架的"缺失补丁"

解决的核心痛点

Java集合框架用了20多年,但一直有个api不一致性问题

// 获取第一个元素 - 每种集合方法都不一样!
List<String> list = new ArrayList<>();
String first1 = list.get(0);  // List用get(0)

Deque<String> deque = new ArrayDeque<>();
String first2 = deque.getFirst();  // Deque用getFirst()

SortedSet<String> sortedSet = new TreeSet<>();
String first3 = sortedSet.first();  // SortedSet用first()

LinkedHashMap<String, String> map = new LinkedHashMap<>();
String first4 = map.entrySet().iterator().next().getKey();  // Map要这么写!!!

// 获取最后一个元素更蛋疼
String last1 = list.get(list.size() - 1);  // List这样
String last2 = deque.getLast();  // Deque这样
String last3 = sortedSet.last();  // SortedSet这样
// Map的最后一个元素?祝你好运!

更痛苦的是反向遍历

// 反向遍历List
for (int i = list.size() - 1; i >= 0; i--) {
    String item = list.get(i);
    // 处理...
}

// 反向遍历LinkedHashMap?需要先转数组再反转!
List<Map.Entry<StringString>> entries = new ArrayList<>(map.entrySet());
Collections.reverse(entries);
for (Map.Entry<StringString> entry : entries) {
    // 处理...
}

技术革新

Java 21引入了三个新接口,统一了"有序集合"的操作:

SequencedCollection (接口)
    ├── List
    ├── Deque
    └── SortedSet

SequencedSet (接口)
    └── SortedSet

SequencedMap (接口)
    └── SortedMap

核心API

  • getFirst() / getLast() - 获取首尾元素
  • addFirst() / addLast() - 添加首尾元素
  • removeFirst() / removeLast() - 移除首尾元素
  • reversed() - 返回反向视图
// 传统方式 - 每种集合的方法都不一样
List<String> list = List.of("A""B""C");
String first = list.get(0);  // List用get(0)
String last = list.get(list.size() - 1);  // 最后一个要这么写

LinkedHashSet<String> set = new LinkedHashSet<>();
String firstSet = set.iterator().next();  // Set要用迭代器
// 最后一个元素?需要遍历整个集合!

LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
Map.Entry<String, Integer> firstEntry = map.entrySet().iterator().next();  // 冗长!

// Java 21方式 - 统一简洁的API
List<String> list21 = new ArrayList<>(List.of("A""B""C""D"));
String first21 = list21.getFirst();    // "A" - 统一的方法名
String last21 = list21.getLast();      // "D"
list21.addFirst("Z");                  // 在开头添加
list21.removeLast();                   // 移除最后一个

// 反向视图 - 优雅!
for (String s : list21.reversed()) {
    System.out.println(s);  // D, C, B, A
}

// LinkedHashMap也统一了
LinkedHashMap<String, Integer> map21 = new LinkedHashMap<>();
map21.put("A", 1);
map21.put("B", 2);
Map.Entry<String, Integer> first = map21.firstEntry();  // A=1
Map.Entry<String, Integer> last = map21.lastEntry();    // B=2
map21.putFirst("Z", 0);  // 在开头插入

// 反向遍历Map
for (var entry : map21.reversed().entrySet()) {
    System.out.println(entry.getKey() + "=" + entry.getValue());
}

值不值得学?

必须学,但5分钟就学会了。

这不是复杂的新概念,就是几个简单的方法。但它能让你的代码更清晰,避免很多低级错误。

3. 记录模式 (Record Patterns) - JEP 440:数据解构的艺术

解决的核心痛点

Java处理嵌套数据结构时的类型转换+字段访问地狱

// 定义数据结构
record Point(int x, int y) {}
record Circle(Point center, int radius) {}
record Rectangle(Point topLeft, Point bottomRight) {}

// 传统方式 - 丑陋的类型检查和字段访问
if (shape instanceof Circle) {
    Circle circle = (Circle) shape;  // 强制转换
    Point center = circle.center();  // 访问字段
    int x = center.x();              // 再访问嵌套字段
    int y = center.y();
    int radius = circle.radius();
    // 终于拿到数据了...
}

这种代码写起来繁琐、读起来费劲、容易出错!

技术革新

记录模式让你可以一步到位地解构数据,就像剥洋葱一样,一层层直接拿到你要的数据:

// Java 21方式 - 直接解构
if (shape instanceof Circle(Point(var x, var y)var radius)) {
    // x, y, radius直接可用!
}
record Point(int x, int y) {}
record Circle(Point center, int radius) {}

// 传统方式 - 层层类型检查和字段访问
if (shape instanceof Circle) {
    Circle circle = (Circle) shape;
    Point center = circle.center();
    int x = center.x();
    int y = center.y();
    int radius = circle.radius();
    // 终于拿到数据了...
}

// Java 21方式 - 直接解构,一步到位
if (shape instanceof Circle(Point(var x, var y)var radius)) {
    // x, y, radius直接可用!
    double area = Math.PI * radius * radius;
    System.out.println("圆心(" + x + "," + y + "),面积" + area);
}

// 结合switch使用更强大
switch (shape) {
    case Circle(Point(var x, var y)var radius) ->
        System.out.println("圆:圆心(" + x + "," + y + "),半径" + radius);
    case Rectangle(Point(var x1, var y1), Point(var x2, var y2)) ->
        System.out.println("矩形:从(" + x1 + "," + y1 + ")到(" + x2 + "," + y2 + ")");
    default -> System.out.println("未知形状");
}

// 实际业务场景:订单处理
record Order(String id, User user, double amount) {}
record User(String name, Address address) {}
record Address(String city, String country) {}

switch (order) {
    // 多层嵌套解构:Order -> User -> Address
    case Order(var id, User(var name, Address(var city, "中国")), var amount)
        when amount > 500 ->
        System.out.println("高价值中国订单:" + id + "," + city + "免费配送");

    default -> System.out.println("普通订单");
}

// 处理树形结构
sealed interface Tree permits Leaf, Branch {}
record Leaf(int value) implements Tree {}
record Branch(Tree left, Tree right) implements Tree {}

int sum(Tree tree) {
    return switch (tree) {
        case Leaf(var value) -> value;
        case Branch(var left, var right) -> sum(left) + sum(right);
    };
}

效果如何?

函数式编程爱好者狂喜!

如果你习惯了Scala、Kotlin、Rust等语言的模式匹配,Java的记录模式会让你感觉"Java终于现代化了"。代码变得更加声明式,一眼就能看出你在匹配什么结构、提取什么数据。

但如果你是纯粹的OOP风格开发者,可能会觉得"这有必要吗?多态不是更好?"

4. Switch模式匹配 (Pattern Matching for switch) - JEP 441:告别类型检查地狱

解决的核心痛点

传统的switch语句只能匹配常量,处理不同类型时只能写一堆if-else:

// 传统方式 - 丑陋的类型检查链
Object obj = getObject();
String result;
if (obj instanceof String) {
    String s = (String) obj;
    result = "字符串:" + s;
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    result = "整数:" + i;
} else if (obj instanceof Long) {
    Long l = (Long) obj;
    result = "长整数:" + l;
} else {
    result = "未知类型";
}

技术革新

Java 21的Switch模式匹配正式转正(经过3次Preview),支持类型模式、守卫子句、null处理:

// 传统方式 - 丑陋的if-else链
Object obj = getObject();
String result;
if (obj == null) {
    result = "空值";
} else if (obj instanceof String) {
    String s = (String) obj;
    if (s.length() > 10) {
        result = "长字符串:" + s;
    } else {
        result = "短字符串:" + s;
    }
} else if (obj instanceof Integer) {
    Integer i = (Integer) obj;
    result = i > 0 ? "正整数:" + i : "非正整数:" + i;
} else {
    result = "其他类型";
}

// Java 21方式 - 简洁的switch表达式
String result = switch (obj) {
    case null -> "空值";  // 直接处理null
    case String s when s.length() > 10 -> "长字符串:" + s;  // when守卫
    case String s -> "短字符串:" + s;
    case Integer i when i > 0 -> "正整数:" + i;
    case Integer i -> "非正整数:" + i;
    case Double d -> "浮点数:" + d;
    default -> "其他类型";
};

// 实际业务场景:HTTP响应处理
sealed interface HttpResponse permits Success, Error, Timeout {}
record Success(int code, String body) implements HttpResponse {}
record Error(int code, String message) implements HttpResponse {}
record Timeout(long duration) implements HttpResponse {}

String message = switch (response) {
    case Success(var code, var body) when code == 200 -> "成功:" + body;
    case Error(var code, var msg) when code == 404 -> "资源不存在";
    case Error(var code, var msg) when code >= 500 -> "服务器错误:" + msg;
    case Timeout(var duration) when duration > 5000 -> "请求超时,请重试";
    case Timeout(var duration) -> "请求超时(" + duration + "ms)";
    default -> "其他响应";
};

// 计算不同类型数据的"大小"
int size = switch (obj) {
    case null -> 0;
    case String s -> s.length();
    case Collection<?> c -> c.size();
    case Map<?, ?> m -> m.size();
    case Object[] arr -> arr.length;
    default -> 1;
};

终于像个现代语言了!

Java的switch终于不再是个"二等公民"。特别是when守卫子句,让你可以在类型匹配的同时加上条件判断,代码简洁度提升了好几个档次。

5. 字符串模板 (String Templates) - JEP 430:告别字符串拼接地狱 [Preview]

解决的核心痛点

Java的字符串拼接太丑陋了

// 传统方式 - 可读性极差
String name = "张三";
int age = 25;
String json = "{"name": "" + name + "", "age": " + age + "}";

// 或者用String.format - 冗长
String formatted = String.format("{"name": "%s", "age": %d}", name, age);

// SQL拼接 - 容易出现SQL注入
String sql = "SELECT * FROM users WHERE name = '" + name + "' AND age = " + age;

其他语言早就有了字符串插值(Python的f-string、JavaScript的模板字符串),Java终于跟上了!

技术革新

Java 21引入字符串模板,但它不仅仅是插值,还提供了自定义处理器来确保安全性:

// ️ 注意:这是Preview特性,需要编译参数 --enable-preview
import static java.lang.StringTemplate.STR;
import static java.lang.StringTemplate.FMT;

String name = "张三";
int age = 25;

// 传统方式 - 丑陋的字符串拼接
String traditional = "姓名:" + name + ",年龄:" + age + ",出生年份:" + (2024 - age);

// Java 21方式 - STR模板处理器
String modern = STR."姓名:{name},年龄:{age},出生年份:{2024 - age}";

// FMT模板处理器 - 支持格式化
String formatted = FMT."薪资:%,10.2f{salary}";

// 多行模板 - 生成JSON
String json = STR."""
    {
      "name": "{name}",
      "age": {age},
      "timestamp": {System.currentTimeMillis()}
    }
    """;

// 生成HTML
String html = STR."""
    <div class="user-card">
      <h2>{name}</h2>
      <p>年龄:{age},状态:{age >= 18 ? "成年" : "未成年"}</p>
    </div>
    """;

// 自定义处理器 - 防止SQL注入(这是字符串模板的杀手锏!)
static final StringTemplate.Processor<String, RuntimeException> SQL = template -> {
    // 将变量替换为参数占位符
    return "SELECT * FROM users WHERE name = ? AND age >= ?";
};

String query = SQL."SELECT * FROM users WHERE name = {username} AND age >= {minAge}";
// 输出:SELECT * FROM users WHERE name = ? AND age >= ?

期待已久,但还在Preview阶段。

字符串模板确实让代码更清晰了,特别是多行字符串的场景。但它最大的价值在于自定义处理器,可以确保SQL安全、HTML转义等,这是其他语言的字符串插值做不到的。

但要注意

  • ️ 还是Preview特性,API可能变化
  • ️ 需要编译参数--enable-preview
  • ️ 生产环境慎用

6. 结构化并发 (Structured Concurrency) - JEP 453:多线程代码的"救世主" [Preview]

解决的核心痛点

传统的多线程编程就是异常处理很麻烦,资源泄露的问题也层出不穷。

// 传统方式 - 手动管理线程,容易出错
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<User> userFuture = executor.submit(() -> fetchUser(userId));
Future<List<Order>> orderFuture = executor.submit(() -> fetchOrders(userId));

try {
    User user = userFuture.get();
    List<Order> orders = orderFuture.get();
} catch (Exception e) {
    // 出错了,但其他任务还在运行!需要手动取消
    userFuture.cancel(true);
    orderFuture.cancel(true);
} finally {
    executor.shutdown();  // 容易忘记
}

技术革新

结构化并发将多个相关任务视为一个工作单元,统一管理生命周期:

import jdk.incubator.concurrent.StructuredTaskScope;

// Java 21方式 - 自动管理,优雅处理
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var userTask = scope.fork(() -> fetchUser(userId));
    var orderTask = scope.fork(() -> fetchOrders(userId));

    scope.join();           // 等待所有任务完成
    scope.throwIfFailed();  // 如果有失败,抛出异常(其他任务自动取消)

    User user = userTask.get();
    List<Order> orders = orderTask.get();
} // 自动清理资源
// ️ 注意:需要添加 --add-modules jdk.incubator.concurrent
import jdk.incubator.concurrent.StructuredTaskScope;

// 传统方式 - 手动管理,容易出错
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<User> userFuture = executor.submit(() -> fetchUser(userId));
Future<List<Order>> orderFuture = executor.submit(() -> fetchOrders(userId));
try {
    User user = userFuture.get();
    List<Order> orders = orderFuture.get();
} catch (Exception e) {
    // 出错了,但其他任务还在运行!需要手动取消
    userFuture.cancel(true);
    orderFuture.cancel(true);
} finally {
    executor.shutdown();  // 容易忘记
}

// Java 21方式 - 自动管理,优雅简洁
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var userTask = scope.fork(() -> fetchUser(userId));
    var orderTask = scope.fork(() -> fetchOrders(userId));
    var pointsTask = scope.fork(() -> fetchPoints(userId));

    scope.join();           // 等待所有任务完成
    scope.throwIfFailed();  // 如果有失败,抛出异常(其他任务自动取消)

    // 获取结果
    User user = userTask.get();
    List<Order> orders = orderTask.get();
    Integer points = pointsTask.get();
} // 自动清理资源

// 超时控制
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    var userTask = scope.fork(() -> fetchUser(userId));
    scope.joinUntil(Instant.now().plusSeconds(3));  // 设置超时
    scope.throwIfFailed();
}

// ShutdownOnSuccess - 谁快用谁(竞速模式)
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
    scope.fork(() -> fetchFromDatabase(query));  // 数据库
    scope.fork(() -> fetchFromCache(query));     // 缓存
    scope.fork(() -> fetchFromAPI(query));       // API
    scope.join();
    return scope.result();  // 返回最快的结果
}

解决了多年的痛点!

结构化并发让多线程代码变得可预测、可维护、不易出错。你不再需要担心资源泄漏、忘记取消任务、复杂的异常处理。

但要注意

  • 还是Preview特性
  • 需要添加incubator模块
  • API可能变化

7. 作用域值 (Scoped Values) - JEP 446:ThreadLocal的现代替代品 [Preview]

解决的核心痛点

ThreadLocal在多线程编程中很有用,但它有严重的问题:

  • 内存泄漏风险:忘记调用remove()会导致内存泄漏
  • 可变性问题:值可以在任何地方被修改,难以追踪
  • 虚拟线程不友好:数百万个虚拟线程 × ThreadLocal = 内存爆炸
// ThreadLocal的问题
ThreadLocal<String> userId = new ThreadLocal<>();
userId.set("user123");
// ...大量代码...
String id = userId.get();
userId.remove();  // 容易忘记!

技术革新

Scoped Values提供了不可变的、作用域限定的线程数据共享机制:

// ️ 注意:需要添加 --add-modules jdk.incubator.concurrent
import jdk.incubator.concurrent.ScopedValue;

// 定义作用域值
private static final ScopedValue<String> USER_ID = ScopedValue.newInstance();
private static final ScopedValue<String> REQUEST_ID = ScopedValue.newInstance();

// ThreadLocal的问题 - 需要手动管理
ThreadLocal<String> userId = new ThreadLocal<>();
try {
    userId.set("user123");
    processRequest();  // 使用userId
} finally {
    userId.remove();  // 容易忘记!导致内存泄漏
}

// Scoped Values方式 - 自动管理,不可变
ScopedValue.where(USER_ID, "user123")
           .where(REQUEST_ID, "req-456")
           .run(() -> {
               // 作用域内可以访问
               String userId = USER_ID.get();
               String requestId = REQUEST_ID.get();
               processRequest(userId, requestId);
           });
// 作用域外自动失效,不会泄漏

// 实际应用:Web请求上下文
static void handleRequest(String userId, String requestId, String tenantId) {
    ScopedValue.where(USER_ID, userId)
               .where(REQUEST_ID, requestId)
               .where(TENANT_ID, tenantId)
               .run(() -> {
                   // 整个请求处理过程中,这些值都可以访问
                   authenticateUser();    // 内部可以通过USER_ID.get()访问
                   queryDatabase();       // 内部可以通过TENANT_ID.get()访问
                   callExternalService(); // 内部可以通过REQUEST_ID.get()访问
               });
    // 请求结束后,作用域值自动失效
}

概念很好,但还不成熟。

Scoped Values解决了ThreadLocal的核心问题:

  • 不可变,更安全
  • 作用域明确,不会泄漏
  • 虚拟线程友好

但它还是Preview特性,API可能变化。

最终评价

Java 21是Java史上最重要的版本之一,堪比Java 8引入Lambda和Stream时的影响力。虚拟线程将彻底改变Java的并发编程模式,而模式匹配让Java的表达力追上了现代编程语言的步伐。

本站提供的所有下载资源均来自互联网,仅提供学习交流使用,版权归原作者所有。如需商业使用,请联系原作者获得授权。 如您发现有涉嫌侵权的内容,请联系我们 邮箱:[email protected]