Java 16 正式发布了 Records 和 instanceof 模式匹配这 2 大特性,让代码更简洁易读。
正式特性
【必备】Records
以前,我们如果想创建一个 POJO 对象来存一些数据,需要编写大量的样板代码,包括构造函数、getter 方法、equals、hashCode、toString 等等,比较麻烦。
// 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,创建数据包装类简直不要太简单,一行代码搞定:
public record Person(String name, int age, String email) {}Records 自动提供了所有必需的方法,使用方式完全一样!6ZM1lNSob3ccjHDvfL4py8zUmrLB7M/zsOtLgHmA1BY=
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 还支持自定义方法和验证逻辑,只不过个人建议这种情况下不如老老实实用 "类" 了。
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 正式推出了 instanceof 的模式匹配,让类型检查和转换变得更优雅。
传统的 instanceof 使用方式,需要显示转换对象类型:Lh1sttvlxPlyty9S4nI+4tAhD1LsVz+UdGrQfAgFY8A=
Object obj = xxx;
if (obj instanceof String) {
String str = (String) obj; // 需要显式转换
return "字符串长度: " + str.length();
}有了 instanceof 模式匹配,可以直接在匹配类型时声明变量:
if (obj instanceof String str) {
return "字符串长度: " + str.length();
}但是要注意,str 变量的作用域被限定在 if 条件为 true 的代码块中,符合最小作用域原则。
模式匹配的高级用法
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() 方法,可以用更简洁的代码将流转换为不可变列表。
// 传统写法
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 的创建开销。
// 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 的实际应用
// 过滤并展开嵌套数据
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 工具正式化,可以创建平台特定的安装包:
# 创建应用程序镜像
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 域套接字的支持:
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 正式支持 Alpine Linux,这对容器化部署很重要:
# 现在可以使用 Alpine 作为基础镜像
FROM openjdk:16-jdk-alpine
COPY myapp.jar /app/
WORKDIR /app
CMD ["java", "-jar", "myapp.jar"]Alpine Linux 的优势:
- 体积小:基础镜像只有几 MB
- 安全性高:攻击面小
- 适合容器:启动快,资源占用少
弹性元空间
Java 16 改进了元空间的内存管理,可以更及时地将未使用的内存返回给操作系统:
# 元空间内存管理是自动的,无需特殊配置
java -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=256m MyApp这个改进可以:
- 减少长期运行应用的内存占用
- 提高容器环境下的内存利用率
- 降低内存压力
默认强封装 JDK 内部
Java 16 默认强封装 JDK 内部 API,提高安全性:
# 如果需要访问内部 API(不推荐)
java --add-opens java.base/sun.nio.ch=ALL-UNNAMED MyApp
# 或者允许所有模块访问(非常不推荐)
java --permit-illegal-access MyApp这个改变鼓励开发者:
- 使用标准 API 而不是内部 API
- 迁移到公开的替代方案
- 提高代码的可移植性
预览特性
【了解】密封类(第二次预览)
Java 16 继续完善密封类特性:
// 密封接口
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("摩托车启动");
}
}密封类的实际应用
// 表示不同类型的 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 的 SIMD 指令:
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,可以更安全地调用本地代码:
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 和 instanceof 模式匹配的正式化大大提升了代码的简洁性和可读性。Stream API 的改进让集合处理更加方便,jpackage 的正式化为应用分发提供了标准解决方案。
虽然 Java 16 不是 LTS 版本,但它的许多特性都成为了现代 Java 开发的基础。Records 特别受欢迎,已经成为创建数据类的首选方式。密封类作为预览特性也展现出了巨大潜力,为类型安全和模式匹配奠定了基础。