Skip to content
<

Java 16 正式发布了 R⁢ecords 和 i⁡nstanceof ⁡模式匹配这 2 大特性,让代码更简洁易读。

正式特性

【必备】Records

以前,我们如果想创建一个 POJO 对象来存一些⁢数据,需要编写大量的样板代码,包⁡括构造函数、getter 方法、⁡equals、hashCode、toString 等等,比较麻烦。

java
// Java 16 之前创建数据类的方式
public class Person {
    private final String name;
    private final int age;
    private final String email;
    
    public Person(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;
        Person person = (Person) obj;
        return age == person.age && 
               Objects.equals(name, person.name) && 
               Objects.equals(email, person.email);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(name, age, email);
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", email='" + email + "'}";
    }
}

即使通过 Lombok 插件简化了代码,估计也要十几行。

有了 Java 16 的 Records,⁢创建数据包装类简直不要太简单⁡,一行代码搞定:      ⁡

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

Records 自动提供了所⁢有必需的方法,使用⁡方式完全一样!6ZM1lNSob3ccjHDvfL4py8zUmrLB7M/zsOtLgHmA1BY=

java
Person person = new Person("鱼皮", 25, "yupi@yuyuanweb.com");
System.out.println(person.name());     // 自动生成的访问器
System.out.println(person.age());
System.out.println(person.email());
System.out.println(person.toString()); // 自动生成的 toString

此外,Records 还支持⁢自定义方法和验证逻⁡辑,只不过个人建议⁡这种情况下不如老老实实用 "类" 了。

java
public record BankAccount(String accountNumber, double balance) {
    // 构造函数中添加验证
    public BankAccount {
        if (balance < 0) {
            throw new IllegalArgumentException("余额不能为负数");
        }
        if (accountNumber == null || accountNumber.isBlank()) {
            throw new IllegalArgumentException("账号不能为空");
        }
    }
    
    // 自定义方法
    public boolean isVIP() {
        return balance > 100000;
    }
    
    // 静态工厂方法
    public static BankAccount createSavingsAccount(String accountNumber) {
        return new BankAccount(accountNumber, 0.0);
    }
}

【了解】instanceof 模式匹配

Java 16 正式推出了 ⁢instanceo⁡f 的模式匹配,让⁡类型检查和转换变得更优雅。

传统的 instanceof⁢ 使用方式,需要显⁡示转换对象类型:Lh1sttvlxPlyty9S4nI+4tAhD1LsVz+UdGrQfAgFY8A=

java
Object obj = xxx;
if (obj instanceof String) {
    String str = (String) obj;  // 需要显式转换
    return "字符串长度: " + str.length();
}

有了 instanceof ⁢模式匹配,可以直接⁡在匹配类型时声明变⁡量:

java
if (obj instanceof String str) {
    return "字符串长度: " + str.length();
}

但是要注意,str 变量的作⁢用域被限定在 if⁡ 条件为 true⁡ 的代码块中,符合最小作用域原则。

模式匹配的高级用法

java
public String processValue(Object value) {
    // 基本模式匹配
    if (value instanceof String str) {
        return "字符串: " + str;
    }
    
    // 结合条件判断
    if (value instanceof String str && str.length() > 5) {
        return "长字符串: " + str;
    }
    
    // 结合逻辑运算符
    if (value instanceof Integer num && num > 0) {
        return "正整数: " + num;
    }
    
    // 否定形式的作用域
    if (!(value instanceof String str)) {
        return "非字符串类型";
    }
    // 这里 str 变量可用,因为前面的条件确保了类型
    return "确认的字符串: " + str;
}

// 在三元运算符中使用
public String formatValue(Object obj) {
    return obj instanceof String str ? str.toUpperCase() : obj.toString();
}

【了解】Stream 新增方法

Java 16 为 Stream API 添加了 toList() 方法,可以用更简洁的代码将流转换为不可变列表。

java
// 传统写法
List<String> result = stream
    .filter(s -> s.length() > 3)
    .collect(Collectors.toList());

// Java 16 简化写法
List<String> result = stream
    .filter(s -> s.length() > 3)
    .toList();  // 返回不可变 List

还提供了 mapMulti() 方法,跟 flatMap 的作用一样,将一个元素映射为 0 个或多个元素,但是某些场景下比 flatMap 更灵活高效。

当需要从一个元素生成多个元素时,flatMap 需要先创建一个中间 Stream,而 mapMulti() 可以通过传入的 Consumer 直接 "推送" 多个元素,避免了中间集合或 Stream 的创建开销。

java
// flatMap 传统方式
List<String> words = List.of("hello", "world", "java");
List<Character> chars = words.stream()
    .flatMap(word -> word.chars()
        .mapToObj(c -> (char) c))
    .toList();

// Java 16 的 mapMulti 方式
List<Character> chars = words.stream()
    .<Character>mapMulti((word, consumer) -> {
        for (char c : word.toCharArray()) {
            consumer.accept(c);  // 直接向下游推送元素
        }
    })
    .toList();

mapMulti 的实际应用

java
// 过滤并展开嵌套数据
List<List<String>> nestedList = List.of(
    List.of("a", "b", "c"),
    List.of("d", "e"),
    List.of("f", "g", "h", "i")
);

// 只保留长度大于2的子列表,并展开
List<String> result = nestedList.stream()
    .<String>mapMulti((subList, consumer) -> {
        if (subList.size() > 2) {
            subList.forEach(consumer);
        }
    })
    .toList();

System.out.println(result); // [a, b, c, f, g, h, i]

【了解】其他正式特性

打包工具正式化

Java 16 将 jpackage 工⁢具正式化,可以创建平台特定⁡的安装包:        ⁡

bash
# 创建应用程序镜像
jpackage --input target/ \
         --name MyApp \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type app-image

# 创建安装程序
jpackage --input target/ \
         --name MyApp \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type msi \
         --app-version 1.0 \
         --description "我的应用程序"

Unix 域套接字通道

Java 16 添加了对 Unix 域套接字的支持:

java
import java.net.UnixDomainSocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;

// 服务器端
Path socketPath = Path.of("/tmp/myapp.socket");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketPath);

try (ServerSocketChannel serverChannel = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
    serverChannel.bind(address);
    
    // 接受连接
    try (SocketChannel clientChannel = serverChannel.accept()) {
        // 处理客户端连接
    }
}

// 客户端
try (SocketChannel clientChannel = SocketChannel.open(StandardProtocolFamily.UNIX)) {
    clientChannel.connect(address);
    // 与服务器通信
}

Unix 域套接字的优势:

  • 性能更好:本地通信无需网络栈
  • 安全性高:基于文件系统权限
  • 可靠性强:不受网络问题影响

Alpine Linux 端口

Java 16 正式支持 A⁢lpine Lin⁡ux,这对容器化部⁡署很重要:

dockerfile
# 现在可以使用 Alpine 作为基础镜像
FROM openjdk:16-jdk-alpine

COPY myapp.jar /app/
WORKDIR /app
CMD ["java", "-jar", "myapp.jar"]

Alpine Linux 的优势:

  • 体积小:基础镜像只有几 MB
  • 安全性高:攻击面小
  • 适合容器:启动快,资源占用少

弹性元空间

Java 16 改进了元空间⁢的内存管理,可以更⁡及时地将未使用的内⁡存返回给操作系统:

bash
# 元空间内存管理是自动的,无需特殊配置
java -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m MyApp

这个改进可以:

  • 减少长期运行应用的内存占用
  • 提高容器环境下的内存利用率
  • 降低内存压力

默认强封装 JDK 内部

Java 16 默认强封装 ⁢JDK 内部 AP⁡I,提高安全性:

bash
# 如果需要访问内部 API(不推荐)
java --add-opens java.base/sun.nio.ch=ALL-UNNAMED MyApp

# 或者允许所有模块访问(非常不推荐)
java --permit-illegal-access MyApp

这个改变鼓励开发者:    ⁢         ⁡         ⁡

  • 使用标准 API 而不是内部 API
  • 迁移到公开的替代方案
  • 提高代码的可移植性

预览特性

【了解】密封类(第二次预览)

Java 16 继续完善密封类特性:

java
// 密封接口
public sealed interface Vehicle 
    permits Car, Truck, Motorcycle {
    void start();
}

// 实现类必须选择策略
public final class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("汽车启动");
    }
}

public sealed class Truck implements Vehicle 
    permits DeliveryTruck, SemiTruck {
    @Override
    public void start() {
        System.out.println("卡车启动");
    }
}

public non-sealed class Motorcycle implements Vehicle {
    @Override
    public void start() {
        System.out.println("摩托车启动");
    }
}

密封类的实际应用

java
// 表示不同类型的 HTTP 响应
public sealed interface HttpResponse 
    permits SuccessResponse, ErrorResponse, RedirectResponse {
}

public record SuccessResponse(int statusCode, String body) implements HttpResponse {}
public record ErrorResponse(int statusCode, String message) implements HttpResponse {}
public record RedirectResponse(int statusCode, String location) implements HttpResponse {}

// 处理响应的方法
public void handleResponse(HttpResponse response) {
    switch (response) {
        case SuccessResponse(var code, var body) -> 
            System.out.println("成功: " + code + " - " + body);
        case ErrorResponse(var code, var message) -> 
            System.err.println("错误: " + code + " - " + message);
        case RedirectResponse(var code, var location) -> 
            System.out.println("重定向: " + code + " -> " + location);
        // 不需要 default,因为所有情况都覆盖了
    }
}

孵化器特性

【了解】向量 API(孵化器)

Java 16 引入了向量 ⁢API,可以利用现⁡代 CPU 的 S⁡IMD 指令:

java
import jdk.incubator.vector.*;

public class VectorExample {
    public static void main(String[] args) {
        VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
        
        float[] a = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f};
        float[] b = {8.0f, 7.0f, 6.0f, 5.0f, 4.0f, 3.0f, 2.0f, 1.0f};
        float[] result = new float[8];
        
        // 向量化加法
        for (int i = 0; i < a.length; i += SPECIES.length()) {
            var va = FloatVector.fromArray(SPECIES, a, i);
            var vb = FloatVector.fromArray(SPECIES, b, i);
            var vc = va.add(vb);
            vc.intoArray(result, i);
        }
        
        System.out.println(Arrays.toString(result)); 
        // [9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0, 9.0]
    }
}

向量 API 的优势:

  • 性能提升:利用 SIMD 指令并行计算
  • 跨平台:JVM 自动选择合适的指令
  • 类型安全:编译时检查向量操作

【了解】外部链接器 API(孵化器)

Java 16 引入了外部链⁢接器 API,可以⁡更安全地调用本地代⁡码:

java
import jdk.incubator.foreign.*;

public class ForeignLinkerExample {
    public static void main(String[] args) throws Throwable {
        // 查找 C 标准库函数
        CLinker linker = CLinker.getInstance();
        LibraryLookup stdlib = LibraryLookup.ofDefault();
        
        // 查找 strlen 函数
        MemoryAddress strlenAddress = stdlib.lookup("strlen").get();
        
        // 创建方法句柄
        FunctionDescriptor descriptor = FunctionDescriptor.of(
            CLinker.C_LONG,     // 返回类型
            CLinker.C_POINTER   // 参数类型
        );
        MethodHandle strlen = linker.downcallHandle(strlenAddress, descriptor);
        
        // 调用本地函数
        try (ResourceScope scope = ResourceScope.newConfinedScope()) {
            MemorySegment cString = CLinker.toCString("Hello World!", scope);
            long length = (long) strlen.invoke(cString);
            System.out.println("字符串长度: " + length); // 12
        }
    }
}

总结

Java 16 是一个重要的版本,Records 和 insta⁢nceof 模式匹配的正式化大大提升了代码⁡的简洁性和可读性。Stream API 的⁡改进让集合处理更加方便,jpackage 的正式化为应用分发提供了标准解决方案。

虽然 Java 16 不是 LTS 版本,但它的许多特性都成为了⁢现代 Java 开发的基础。Records⁡ 特别受欢迎,已经成为创建数据类的首选方式⁡。密封类作为预览特性也展现出了巨大潜力,为类型安全和模式匹配奠定了基础。