JDK新版本特性

版本

年份

LTS

关键特性

影响力等级

Java 8

2014

Lambda 表达式、Stream API、方法引用、接口默认方法、Optional 类

⭐⭐⭐⭐⭐

Java 9

2017

模块化系统(JPMS)、JShell、集合工厂方法、接口私有方法

⭐⭐⭐⭐

Java 10

2018

var

局部变量类型推断、G1 并行 Full GC

⭐⭐⭐

Java 11

2018

HTTP Client 正式版、单文件运行、ZGC(实验)、 String增强

⭐⭐⭐⭐

Java 12

2019

Switch 表达式(预览)、Shenandoah GC(实验)

⭐⭐

Java 13

2019

文本块(预览)

⭐⭐

Java 14

2020

instanceof

模式匹配(预览)、Record 类(预览)

⭐⭐⭐

Java 15

2020

文本块(正式)、Sealed Classes(预览)

⭐⭐

Java 16

2021

Record 类(正式)、Stream.toList()

⭐⭐⭐

Java 17

2021

Sealed Classes(正式)、模式匹配 Switch(预览)、移除 Applet

⭐⭐⭐⭐

Java 18

2022

简单 Web 服务器(jwebserver)、默认 UTF-8

Java 19

2022

虚拟线程(预览)、结构化并发(预览)

⭐⭐⭐⭐

Java 20

2023

Scoped Values(预览)、Record 模式匹配(预览)

⭐⭐

Java 21

2023

虚拟线程(正式)、序列集合 API、分代 ZGC

⭐⭐⭐⭐⭐

Java 22

2024

未命名变量(预览)、String 模板(预览)

⭐⭐

Java 23

2024

Vector API(正式)、分代 ZGC 优化

⭐⭐⭐

Java8

函数式革命

Java 8是Java历史上最重要的一次版本更新,它将函数式编程的核心思想融入了这个传统的面向对象语言,从根本上改变了Java代码的编写方式。

1.1 范式转变:Lambda表达式与函数式接口

Java 8的核心变革是引入了Lambda表达式,这是一种简洁的匿名函数语法,允许将行为(代码块)作为方法参数传递,或者像数据一样存储 。这一特性极大地减少了使用匿名内部类时所需的大量样板代码。

Lambda表达式的基石是函数式接口(Functional Interface),即任何只包含一个抽象方法的接口(Single Abstract Method, SAM)。@FunctionalInterface注解可以确保接口满足SAM的条件,在编译时防止开发者意外地添加第二个抽象方法,从而保证了接口的函数式特性

一个经典的例子是使用Comparator对集合进行排序:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// Java 8 之前:使用匿名内部类
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.compareTo(s2);
    }
});

// Java 8:使用Lambda表达式
Collections.sort(names, (s1, s2) -> s1.compareTo(s2));

// 进一步简化:使用方法引用
Collections.sort(names, String::compareTo);

此外,Java 8在java.util.function包中提供了一组预定义的、通用的函数式接口,如Predicate<T>(接收T返回boolean)、Function<T, R>(接收T返回R)、Consumer<T>(接收T无返回)和Supplier<R>(不接收参数返回R),它们覆盖了绝大多数日常开发场景

1.2 Stream API:声明式数据处理

Stream API是Java 8的另一大支柱,它提供了一种声明式的、流畅的(fluent)方式来处理数据集合 。开发者不再需要编写复杂的

for循环来控制迭代过程(命令式),而是可以专注于“做什么”(声明式)。

一个Stream管道通常包含三个阶段:

  1. 数据源(Source):从集合(collection.stream())、数组、I/O通道(如Files.lines())等创建一个流
  2. 中间操作(Intermediate Operations):这些操作是惰性的(lazy),它们返回一个新的流,并且只有在终端操作被调用时才会执行。常见的中间操作包括filter(过滤)、map(映射/转换)、sorted(排序)和distinct(去重)
  3. 终端操作(Terminal Operations):这些操作是及早的(eager),它们会触发整个管道的计算,并产生一个最终结果或副作用。常见的终端操作包括forEach(遍历)、collect(收集到集合)、reduce(规约)、sum(求和)和anyMatch(任意匹配)

以下示例展示了如何使用Stream API筛选出特定类型的交易,并计算其总金额:

List<Transaction> transactions =...;

// 使用Stream API进行声明式处理
int totalValue = transactions.stream()                               // 1. 创建流 (数据源)
    .filter(t -> t.getType() == Transaction.GROCERY)                 // 2. 过滤出杂货类交易 (中间操作)
    .mapToInt(Transaction::getValue)                                 // 2. 提取每笔交易的金额 (中间操作)
    .sum();                                                          // 3. 求和 (终端操作)

1.3 演进的接口:默认方法与静态方法

为了在不破坏向后兼容性的前提下为现有接口添加新功能,Java 8引入了默认方法(Default Methods)和静态方法(Static Methods)。这一特性至关重要,它使得Java团队能够为Collection等核心接口添加stream()forEach()等新方法,而不会导致数百万已有的实现类编译失败。

默认方法使用default关键字修饰,它允许在接口中提供一个默认的方法实现。实现该接口的类将自动继承这个默认实现,也可以根据需要选择性地覆盖它。

interface Vehicle {
    void start();
    void stop();

    // 默认方法提供了一个基础实现
    default void horn() {
        System.out.println("Beep beep!");
    }
}

class Car implements Vehicle {
    @Override
    public void start() { /*... */ }

    @Override
    public void stop() { /*... */ }

    // Car类自动继承了horn()方法,无需实现
}

1.4 现代化的空值处理:Optional

为了解决Java中长期存在的NullPointerException(NPE)问题,Java 8引入了java.util.Optional<T>

Optional是一个容器对象,它可能包含一个非空值,也可能为空。其设计意图并非完全消灭null,而是通过类型系统来显式地表示一个值可能缺失的情况,从而强迫API使用者正视并处理这种可能性。

常用的Optional方法包括of()(用于非空值)、ofNullable()(可接受null)、isPresent()(检查是否有值)、ifPresent()(有值时执行操作)、orElse()(无值时返回默认值)和orElseThrow()(无值时抛出异常)。

Java

public Optional<User> findUserById(String id) {
    //... 查找用户的逻辑
    User user =...; // 可能是null
    return Optional.ofNullable(user);
}

// 使用Optional API安全地处理可能缺失的用户
User user = findUserById("123").orElse(new GuestUser()); // 如果找不到用户,则返回一个访客用户

1.5 时间的新纪元:java.time API (JSR 310)

Java 8用一套全新的、设计精良的Date/Time API (java.time包) 彻底取代了旧有的、问题缠身的java.util.Datejava.util.Calendar。新的API基于ISO-8601标准,其核心特点是

不可变性(immutability)线程安全,这从根本上解决了旧API的诸多设计缺陷。

核心类包括:

  • LocalDate: 表示日期(年-月-日)。
  • LocalTime: 表示时间(时-分-秒)。
  • LocalDateTime: 表示日期和时间。
  • ZonedDateTime: 表示带时区的日期和时间。
  • Duration: 表示以秒和纳秒为单位的时间间隔。
  • Period: 表示以年、月、日为单位的日期间隔。
// 创建、操作和格式化日期时间
LocalDateTime now = LocalDateTime.now();
LocalDateTime tomorrow = now.plusDays(1);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formattedTomorrow = tomorrow.format(formatter);

System.out.println("Formatted tomorrow: " + formattedTomorrow);

Java 8的这些特性并非孤立存在,它们共同构成了一个紧密协作的生态系统。其设计的内在逻辑是:为了实现Stream API的声明式数据处理能力,需要一种方式来传递行为,这催生了函数式接口。为函数式接口提供简洁的实现语法,则诞生了Lambda表达式。而为了将stream()等方法无缝地集成到现有的Collection接口中,同时不破坏兼容性,默认方法应运而生。这一系列环环相扣的设计,共同完成了Java向函数式编程范式的华丽转身。

Java11

现代化与清理 (2018年9月发布)

作为新发布周期下的第一个LTS版本,Java 11承担了“承上启下”的重任。它巩固了Java 9和10引入的重要特性,同时对JDK进行了大刀阔斧的清理和现代化改造。

2.1 简洁与清晰:局部变量类型推断 (var)

Java 11正式确立了在Java 10中引入的var关键字

var允许在声明局部变量时,由编译器根据赋值表达式的右侧自动推断变量类型 。这显著减少了代码的冗余,尤其是在处理复杂的泛型类型时,能让代码更加整洁。

Java

// Java 11 之前
Map<String, List<Integer>> myMap = new HashMap<String, List<Integer>>();

// 使用 var 关键字
var myMap = new HashMap<String, List<Integer>>();

更进一步,Java 11将var的适用范围扩展到了Lambda表达式的参数上。这使得我们可以在Lambda参数上添加注解,而无需显式写出完整的类型 。

// 在Lambda参数上使用var来添加注解
var list = List.of("a", "b");
String result = list.stream()
                .map((@Nonnull var s) -> s.toUpperCase()) // @Nonnull是示例注解
                .collect(Collectors.joining(","));

(代码灵-感来源于 )

2.2 现代化的Web栈:标准化的HTTP客户端

Java 11将一个全新的、现代化的HTTP客户端API(位于java.net.http包)正式纳入标准库,用以取代老旧且功能有限的HttpURLConnection。该API在Java 9和10中作为孵化模块进行了充分的社区验证。

其主要特性包括:

  • 流畅的构建器(Builder)风格API。
  • 原生支持HTTP/1.1和HTTP/2协议。
  • 通过CompletableFuture提供异步(非阻塞)操作。
  • 支持WebSocket。

以下代码展示了如何发送一个异步的GET请求:

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create("https://api.example.com/data"))
                    .build();

// 异步发送请求,并通过CompletableFuture处理响应
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                    .thenApply(HttpResponse::body)
                    .thenAccept(System.out::println)
                    .join(); // 等待异步操作完成

2.3 API精炼:String、File和Collection的增强

Java 11为多个核心API添加了实用的新方法,提升了日常开发的便利性。

  • String API增强:新增了isBlank()(判断字符串是否为空白)、lines()(将字符串按行分割成流)、strip()(去除首尾空白,支持Unicode)、stripLeading()stripTrailing()repeat(n)(重复字符串n次)等方法
  • File API增强:新增了Files.writeString()Files.readString()两个静态方法,极大地简化了小文件的读写操作
  • Predicate API增强:增加了静态方法Predicate.not(),提供了一种更具可读性的方式来否定一个谓词,尤其在Stream API中使用时
  • Collection API增强Collection接口新增了默认方法toArray(IntFunction<T> generator),为将集合转换为特定类型的数组提供了更简洁的语法

String multilineText = "  Line1\n  Line2\n\n  Line3  ";

// 结合使用多个新API
List<String> processedLines = multilineText.lines()
   .filter(Predicate.not(String::isBlank)) // 使用Predicate.not()
   .map(String::strip)                     // 使用strip()
   .collect(Collectors.toList());

// 结果: ["Line1", "Line2", "Line3"]
System.out.println(processedLines);

2.4 简化开发流程:单文件源码执行

Java 11引入了一个重要的便利性特性:可以直接使用java命令执行单个.java源文件,而无需先使用javac进行编译 。这对于学习、编写小型脚本或快速原型验证非常有用。

Bash

# 假设有一个 HelloWorld.java 文件

# Java 11 之前的执行方式
# > javac HelloWorld.java
# > java HelloWorld

# Java 11 的新方式
# > java HelloWorld.java

(代码灵感来源于 )

2.5 基础性变革:JVM改进与模块移除

Java 11最深远的变化之一,是从核心JDK中移除了Java EE和CORBA模块。这包括

java.xml.ws (JAX-WS)、java.xml.bind (JAXB)等。这些技术现在需要作为独立的第三方库添加到项目中。

在JVM层面,Java 11引入了两个新的实验性垃圾收集器:

  • Epsilon GC:一个“无操作”(No-Op)的垃圾收集器,它只分配内存而不进行回收。主要用于性能测试和分析内存分配的开销
  • ZGC (Z Garbage Collector):一个可伸缩的、低延迟的垃圾收集器,设计目标是处理从几百MB到数TB的巨大堆内存,同时将GC暂停时间控制在10毫秒以内

此外,**基于嵌套的访问控制(Nest-Based Access Control)**优化了JVM对嵌套类(内部类)私有成员的访问机制,使其更高效、更安全

Java 11的这些变革标志着JDK发展战略的一次重要转向。通过移除Java EE模块,Oracle明确了核心JDK将专注于语言本身和核心平台,而将企业级功能完全交由社区驱动的外部规范和库(如Jakarta EE)来提供。这一举措虽然在初期给依赖这些模块的开发者带来了一些迁移阵痛,但从长远来看,它使JDK变得更加轻量和模块化,解耦了核心平台与企业级技术栈的发布周期,迫使开发者采用更现代的依赖管理实践,最终促进了整个Java生态系统的健康发展。

Java17

Java 17 – 表现力强的数据建模与安全性 (2021年9月发布)

Java 17引入了一套强大的语言特性——Records、Sealed Classes和增强的模式匹配,它们协同工作,彻底改变了开发者建模数据和编写类型安全代码的方式。

记录类 Record

全新的类型声明,它旨在充当透明、不可变的纯数据载体

在传统 Java 开发中,创建一个简单的数据类需要编写大量模板代码:空参构造,有参构造,equals(),hashCode(),toString()方法,getter,setter

public class Person {
    private final String name;
    private final int age;
    
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() { return name; }
    public int getAge() { return age; }
    
    @Override
    public boolean equals(Object o) {
        // 长长的equals实现...
    }
    
    @Override
    public int hashCode() {
        // hashCode实现...
    }
    
    @Override
    public String toString() {
        return "Person[name=" + name + ", age=" + age + "]";
    }
}

Record 的基本语法与使用

JDK 17 中的记录类(Record)彻底解决了这个问题:

public record Person(String name, int age) {}

就这么简单!编译器会自动为你生成构造器、getter 方法、equals()、hashCode() 和 toString() 方法。这一行代码等同于前面的几十行代码

Record 与不可变对象

Record 天生是不可变的,这符合函数式编程的理念,有助于编写线程安全的代码。如果你需要修改记录的某个字段,只能创建一个新的实例:

Person alice = new Person("Alice", 25);
// 想要修改年龄?创建一个新实例
Person olderAlice = new Person(alice.name(), alice.age() + 1);

何时使用与不使用 Record

Record 非常适合作为 DTOs(数据传输对象) 、POJOs 、值对象或不可变数据容器。但它也有局限性:不能继承其他类(隐式final),不能声明实例字段(除了在构造函数中定义的),不能是抽象的。如果你需要这些特性,还是应该使用传统类。

密封类(Sealed Classes)

密封类的核心概念

在 Java 中,一个类要么是 final 的(不能被继承),要么可以被任何类继承。密封类(Sealed Classes)提供了一种中间状态:你可以指定哪些类可以继承它。

public sealed class Shape permits Circle, Rectangle, Triangle {
    // 共享方法和属性
}

permits 关键字详解

permits 关键字明确列出了允许继承该密封类的所有子类。子类必须使用 finalsealednon-sealed 修饰符来声明自己的继承策略:

public final class Circle extends Shape {
    // Circle不能再被继承
}

public sealed class Rectangle extends Shape permits Square {
    // Rectangle只能被Square继承
}

public non-sealed class Triangle extends Shape {
    // Triangle可以被任何类继承
}

与接口结合使用

密封特性也适用于接口:

public sealed interface Vehicle permits Car, Truck, Motorcycle {
    void move();
}

实际应用案例

密封类非常适合领域建模, 对于领域驱动设计、确保API的完整性以及启用详尽的模式匹配至关重要 ,特别是当你有一个封闭的类型集合时:

public sealed interface PaymentMethod permits CreditCard, DebitCard, BankTransfer, DigitalWallet {
    boolean processPayment(double amount);
}

public final class CreditCard implements PaymentMethod {
    @Override
    public boolean processPayment(double amount) {
        // 信用卡支付逻辑
        return true;
    }
}

// 其他实现类...

这样,当你使用 switch 语句处理不同的支付方式时,编译器可以确保你已经处理了所有可能的情况。

模式匹配

类型模式匹配

在 JDK 17 之前,使用 instanceof 进行类型检查后,我们还需要显式地进行类型转换:

// 旧方式
if (obj instanceof String) {
    String s = (String) obj;
    if (s.length() > 5) {
        // 使用字符串 s
    }
}

JDK 17 引入了模式匹配,不仅能测试一个对象的类型,还能在测试成功后,将该对象绑定到一个该类型的新变量上,从而省去了显式的强制类型转换步骤 。

// 新方式
if (obj instanceof String s && s.length() > 5) {
    // 直接使用字符串 s
}

switch 表达式增强

JDK 17 中的 switch 也支持了模式匹配:

bject obj = getSomeObject();
String result = switch (obj) {
    case Integer i -> "整数: " + i;
    case String s -> "字符串: " + s;
    case Person p -> "人: " + p.name();
    default -> "未知类型";
};

性能考量

模式匹配不仅提高了代码可读性,而且在许多情况下还能提升性能,因为编译器可以对模式匹配进行优化,减少冗余的类型检查。

文本块

传统字符串拼接的问题

在 JDK 15 之前,处理多行字符串是一件痛苦的事情:

String html = "<html>\n" +
"    <body>\n" +
"        <h1>Hello, World!</h1>\n" +
"    </body>\n" +
"</html>";

这种代码不仅难以维护,而且容易出错。

文本块语法详解

JDK 17 中的文本块(Text Blocks)让多行字符串变得简单:

String html = """
              <html>
                  <body>
                      <h1>Hello, World!</h1>
                  </body>
              </html>
              """;

文本块以三个双引号开始和结束,中间的内容可以包含任意字符,包括换行符和引号,无需转义。

格式控制技巧

文本块会自动删除每行开头的公共空白,但你可以通过 \s 来保留空格,或使用 “ 来连接行:

String query = """
               SELECT id, name, email \
               FROM users \
               WHERE status = 'ACTIVE' \
               ORDER BY name""";

JSON、SQL 和 HTML 处理实例

文本块特别适合处理结构化文本:

// JSON示例
String jsonConfig = """
                    {
                        "appName": "神仙应用",
                        "version": "1.0.0",
                        "features": [
                            "记录类",
                            "密封类",
                            "模式匹配"
                        ]
                    }
                    """;

// SQL示例
String sql = """
             SELECT p.name, p.age, a.city
             FROM persons p
             JOIN addresses a ON p.id = a.person_id
             WHERE a.country = '中国'
               AND p.age > 18
             """;

var 与增强型 switch

类型推断的魅力

虽然 var 是在 JDK 10 中引入的,但它与 JDK 17 的其他特性结合使用时,可以让代码更加简洁:

// 不使用var
Map<String, List<Person>> groupedPeople = new HashMap<>();

// 使用var
var groupedPeople = new HashMap<String, List<Person>>();

switch 表达式与 yield

JDK 17 中的 switch 可以作为表达式使用,并且可以直接返回值:

int dayOfWeek = 3;
String day = switch (dayOfWeek) {
    case 1 -> "星期一";
    case 2 -> "星期二";
    case 3 -> "星期三";
    case 4 -> "星期四";
    case 5 -> "星期五";
    case 6, 7 -> "周末";
    default -> "无效日期";
};

如果需要更复杂的逻辑,可以使用代码块和 yield 关键字:

String result = switch (status) {
    case "PENDING" -> {
        log.info("处理待定状态");
        yield "处理中";
    }
    case "APPROVED" -> {
        log.info("处理已批准状态");
        yield "已完成";
    }
    default -> "未知状态";
};

箭头语法与多分支处理

新的 switch 语法支持使用箭头 -> 来简化代码,并且可以在一个 case 中处理多个值:

Season season = switch (month) {
    case 3, 4, 5 -> Season.SPRING;
    case 6, 7, 8 -> Season.SUMMER;
    case 9, 10, 11 -> Season.AUTUMN;
    case 12, 1, 2 -> Season.WINTER;
    default -> throw new IllegalArgumentException("无效月份");
};

代码可读性的平衡

虽然这些新特性可以让代码更简洁,但也要注意不要过度使用,导致代码难以理解。保持适度,让代码既简洁又清晰。

其他实用特性大集合

私有接口方法

从 JDK 9 开始,接口可以包含私有方法,这在实现默认方法时非常有用:

public interface Logger {
    default void logInfo(String message) {
        log(message, "INFO");
    }

    default void logError(String message) {
        log(message, "ERROR");
    }

    // 私有辅助方法
    private void log(String message, String level) {
        System.out.println("[" + level + "] " + message);
    }
}

改进的 Stream API

JDK 17 中的 Stream API 增加了一些实用方法:

// 将流转换为List(不需要再调用collect(Collectors.toList()))
List<String> names = people.stream()
                          .map(Person::name)
                          .filter(name -> name.startsWith("张"))
                          .toList();

// 新的mapMulti方法,可以为每个元素生成多个结果
List<String> words = sentences.stream()
                             .mapMulti((sentence, consumer) -> {
                                 for (String word : sentence.split(" ")) {
                                     consumer.accept(word);
                                 }
                             })
                             .toList();

增强的 NullPointerException

JDK 17 中的 NullPointerException 会提供更详细的错误信息,指出哪个变量是 null:

// 旧版本的错误信息
Exception in thread "main" java.lang.NullPointerException

// JDK 17 的错误信息
Exception in thread "main" java.lang.NullPointerException: 
Cannot invoke "Person.getName()" because "person" is null

这大大提高了调试效率,不再需要猜测哪个对象是 null。

新的垃圾收集器

JDK 17 提供了多种垃圾收集器选项,包括 ZGC(Z Garbage Collector),它能够处理 TB 级别的堆内存,同时保持低于 10ms 的暂停时间:

// 启用ZGC的JVM参数
-XX:+UseZGC

外部内存访问 API

JDK 17 引入了外部内存访问 API,允许 Java 程序安全地访问堆外内存:

// 分配堆外内存
try (MemorySegment segment = MemorySegment.allocateNative(100)) {
    // 写入数据
    MemoryAccess.setInt(segment, 0, 42);

    // 读取数据
    int value = MemoryAccess.getInt(segment, 0);
    System.out.println(value); // 输出: 42
}

这对于需要与本地代码交互或处理大量数据的应用程序特别有用。

Java 17的这三大特性并非偶然的组合,它们共同构成了一个精心设计的、内聚的工具集。这个工具集为Java带来了代数数据类型(Algebraic Data Types, ADTs) 的强大能力,这是Haskell、Scala等函数式语言中的核心概念。其内在逻辑是:Sealed Classes/Interfaces 扮演了“和类型”(Sum Type)的角色,定义了一个类型是“这几种可能之一”(例如,Shape要么是Circle,要么是Rectangle)。Records 则完美地扮演了“积类型”(Product Type)的角色,定义了一个类型是“这几个部分之和”(例如,Rectanglewidthheight的组合)。最后,switch的模式匹配 提供了对ADTs进行“解构”的机制,它允许开发者安全、详尽地处理每一种可能性,并由编译器来保证逻辑的完备性。这标志着Java在数据建模和类型安全方面迈出了革命性的一步。

Java21

Java 21 – 下一代并发与工效学 (2023年9月发布)

作为最新的LTS版本,Java 21带来了Java历史上最受期待的特性之一——虚拟线程,同时对集合框架和模式匹配进行了重大的易用性改进。

4.1 并发革命:虚拟线程 (Project Loom)

虚拟线程是由JVM而非操作系统内核管理的轻量级线程。创建和阻塞虚拟线程的成本极低,这使得开发者可以轻松地采用“一个请求一个线程(thread-per-request)”的编程模型,并将其扩展到数百万个并发任务,而资源开销却极小。

虚拟线程解决了长期以来并发编程中的一个核心矛盾:易于编写和调试的阻塞式代码(在传统平台线程下伸缩性差)与性能优越但极其复杂的异步非阻塞代码(如回调地狱)之间的矛盾。虚拟线程让开发者能够用简单的阻塞式代码,获得异步代码的伸缩性。

使用新的Executors.newVirtualThreadPerTaskExecutor()可以轻松创建和管理虚拟线程:

// 模拟处理10,000个并发请求
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    IntStream.range(0, 10_000).forEach(i -> {
        executor.submit(() -> {
            // 每个任务都在一个虚拟线程中运行
            System.out.println("Task " + i + " started.");
            Thread.sleep(Duration.ofSeconds(1)); // 阻塞操作在这里是廉价的
            System.out.println("Task " + i + " complete.");
            return i;
        });
    });
} // try-with-resources 会自动关闭executor并等待所有任务完成

4.2 有序数据的统一API:序列化集合

Java 21引入了一套新的接口:SequencedCollectionSequencedSetSequencedMap。它们为具有确定出现顺序的集合提供了一个统一的API

ListDequeLinkedHashSetLinkedHashMap等现有集合类都实现了这些新接口。这套API提供了getFirst()getLast()addFirst()addLast()reversed()等通用方法,开发者不再需要根据具体实现类去猜测或编写特定的代码来访问集合的首尾元素。

// List接口现在扩展了SequencedCollection
List<String> list = new ArrayList<>(List.of("A", "B", "C"));

// 直接调用新接口定义的方法
System.out.println("First element: " + list.getFirst()); // 输出 "A"
System.out.println("Last element: " + list.getLast());   // 输出 "C"

// reversed()返回一个反向顺序的视图,而不是一个新集合的拷贝
SequencedCollection<String> reversedView = list.reversed();
System.out.println("Reversed view: " + reversedView); // 输出 ""

list.addFirst("X"); // 在列表开头添加元素
System.out.println("After addFirst: " + list); // 输出 ""

4.3 数据导航的完善:Record模式与未命名模式

Java 21最终确定了Record模式,允许在instanceofswitch中直接解构record实例,无需调用访问器方法即可提取其组件

同时,引入了未命名模式和变量,使用下划线_作为占位符,表示某个模式或变量是有意被忽略的 。这不仅清理了代码,也向编译器和读者明确传达了“这个组件我不在乎”的意图。

record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}

void process(Object obj) {
    switch (obj) {
            // Record模式解构了ColoredPoint及其嵌套的Point
            // 未命名模式 `_` 忽略了Color组件
        case ColoredPoint(Point(int x, int y), _) ->
            System.out.printf("Point at x=%d, y=%d%n", x, y);

        case String s ->
            System.out.println("String: " + s);

            // 未命名变量 `_` 用于捕获异常但不使用它
        case NumberFormatException _ ->
            System.out.println("Invalid number format");

        default -> { /*... */ }
    }
}

4.4 未来一瞥:预览与孵化特性

Java 21还包含了一些重要的预览(Preview)和孵化(Incubator)特性,它们预示了Java语言未来的发展方向

  • 字符串模板(预览):一种更安全、更富表现力的字符串插值方式,可以防止注入漏洞。例如:String message = STR."Hello \{name}!";
  • 结构化并发(预览):一个API,用于将不同线程中运行的相关任务组视为一个单一的工作单元,从而简化错误处理、取消和可靠性管理
  • 外部函数与内存API(预览):一个安全、高效的API,用于与JVM之外的代码和数据(如本地C库)进行互操作

Java 21的虚拟线程是Java平台的一项重大战略举措。它旨在与Go语言的goroutine等现代并发模型直接竞争。长期以来,Java应对高并发的方案是复杂的异步框架,学习曲线陡峭。虚拟线程的出现,旨在通过提供一种既能获得异步模型的高伸缩性,又能保持同步阻塞模型简单性和可读性的方案,来重新赢回开发者在构建高并发应用时的青睐 。这极大地降低了构建可伸缩服务的门槛,并使得大量现有的同步代码库能够几乎无缝地获得性能提升。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部