java record
Java Record 是从 Java 14 开始预览,并在 Java 16 正式发布的特性。它是一种专门用于承载不可变数据的轻量级类,可以看作是一种“具名元组”。它的核心使命是简化数据载体类(Data Carrier)的编写,让你告别为 POJO 重复编写构造器、Getter、equals、hashCode 和 toString 等样板代码的痛苦。
简单来说,以前写一个只有两个字段的类需要几十行代码,用 Record 只需一行。
🔍 核心特性与语法
定义一个 Record 使用 record 关键字:
public record Person(String name, int age) {}
仅此一行,编译器会自动为你生成以下所有内容:
- 全部字段:
private final字段,确保不可变性。 - 规范构造器:参数顺序与声明顺序一致。
- 自动 Getter:方法名就是字段名,如
name()、age(),没有get前缀。 equals()与hashCode():基于所有组件(字段)值的实现。toString():格式为Person[name=..., age=...]的字符串。
它的使用方式和普通类几乎一样,但更具声明性:
Person person = new Person("Alice", 30);
String name = person.name(); // 调用自动生成的Getter
System.out.println(person); // Person[name=Alice, age=30]
🏗️ 定制与增强
Record 并非完全死板,它允许在紧凑语法上进行有限的定制。
1. 自定义构造器(紧凑构造器)
你可以添加验证逻辑,不需要指定参数列表,也不能给字段赋值(编译器会自动完成):
public record Person(String name, int age) {
// 这就是紧凑构造器,可以加逻辑
public Person {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
// 无需写 this.name = name,编译器会自动生成在末尾
}
}
2. 添加实例方法或静态方法
public record Person(String name, int age) {
public String nameToUpperCase() {
return name.toUpperCase();
}
public static Person fromAnonymous() {
return new Person("Anonymous", 0);
}
}
3. 实现接口
Record 可以自由地实现接口,参与多态。
public interface Greetable { String greet(); }
public record Person(String name, int age) implements Greetable {
@Override
public String greet() { return "Hi, I'm " + name; }
}
⚖️ 主要限制
为了实现“纯粹的数据载体”目标,Record 有几条硬性限制,以保证语义的纯粹性:
- 不可继承:Record 默认是
final的,且无法继承其他类。因为它已隐式继承java.lang.Record父类。 - 所有字段都是
final:保证实例一旦创建就不可变。 - 不能有实例字段:除了在记录头中声明的组件(字段),不能再添加其他实例变量。
✅ 适用场景
当你需要创建如下类型的对象时,Record 是完美的选择:
- DTO (数据传输对象):在应用层间传递数据。
- VO (值对象):用于领域驱动设计,根据属性来识别身份。
- API 的返回值:临时组合查询结果。
- Map 的复合键:当键由多个值决定时,Record 能自动提供正确的
equals和hashCode。
📝 与其他概念的对比
| 特性 | Java Record | 传统 POJO | Lombok @Value |
|---|---|---|---|
| 语法简洁度 | 极高,一行声明 | 极低,需 IDE 生成或手写 | 高,依赖注解处理 |
| 不可变性 | 语言级保证 | 需手动实现 | 注解级保证 |
| 本质 | 一种新的类类型 | 普通类 | 编译期代码增强 |
| 继承限制 | final 类,仅能实现接口 |
无限制 | 无限制 |
💡 代码示例:在 Map 中作为键
import java.util.HashMap;
import java.util.Map;
public class RecordDemo {
public static void main(String[] args) {
// 定义坐标记录
record Coordinate(int x, int y) {}
var map = new HashMap<Coordinate, String>();
map.put(new Coordinate(1, 2), "宝藏");
// 因为equals/hashCode基于x,y的值,所以能正确取出
System.out.println(map.get(new Coordinate(1, 2))); // 输出:宝藏
}
}
总的来说,Java Record 让你在处理纯数据时,代码更简洁、意图更清晰、不可变性更安全。它很适合用来替代那些只需要承载数据的传统类。
Record 本身设计为最终类,如果你需要为基于 Record 的模型提供序列化支持,想了解如何与 Jackson 等 JSON 库搭配使用吗?
当需要将 Java Record 序列化成 JSON 或将 JSON 反序列化为 Record 时,Jackson 提供了很好的支持。下面介绍几种常用方式。
📦 添加依赖
在 pom.xml 中添加 Jackson 依赖(以 2.15 为例):
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
✅ 基础使用:开箱即用
从 Jackson 2.12 开始,对 Record 的支持是内置且无需额外注解的,可以直接序列化和反序列化。
1. Record 定义
public record Person(String name, int age, String email) {}
2. 序列化与反序列化
import com.fasterxml.jackson.databind.ObjectMapper;
public class RecordJsonDemo {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// Java Record -> JSON 字符串
Person person = new Person("张三", 28, "zhangsan@example.com");
String json = mapper.writeValueAsString(person);
System.out.println("JSON: " + json);
// 输出: JSON: {"name":"张三","age":28,"email":"zhangsan@example.com"}
// JSON 字符串 -> Java Record
String jsonStr = "{\"name\":\"李四\",\"age\":35,\"email\":\"lisi@example.com\"}";
Person deserializedPerson = mapper.readValue(jsonStr, Person.class);
System.out.println("反序列化: " + deserializedPerson);
// 输出: 反序列化: Person[name=李四, age=35, email=lisi@example.com]
}
}
🎨 定制字段名称:@JsonProperty
若 JSON 字段名与 Record 组件名不一致,使用 @JsonProperty:
public record Person(
@JsonProperty("full_name") String name,
@JsonProperty("years_old") int age,
String email
) {}
🏗️ 自定义构造器反序列化
对于需要验证或转换的场景,可以在紧凑构造器中处理:
import com.fasterxml.jackson.annotation.JsonCreator;
public record Person(String name, int age, String email) {
@JsonCreator // 明确指示 Jackson 使用此构造器
public Person {
if (age < 0) throw new IllegalArgumentException("年龄非法");
// 可以做一些规范化,比如 trim
}
}
🧩 空值处理与可选字段
使用 Optional 需特别注意:Jackson 默认能处理 Optional 类型,但更推荐用 @JsonInclude 控制非空序列化,或在反序列化时给默认值。
为字段设置默认值(反序列化时若 JSON 缺失该字段):
public record Person(String name, int age, String email) {
// 手工重载规范构造器,在内部提供默认值
@JsonCreator
public Person(@JsonProperty("name") String name,
@JsonProperty("age") int age,
@JsonProperty("email") String email) {
this.name = name == null ? "未知" : name;
this.age = age;
this.email = email == null ? "no-email@example.com" : email;
}
}
⚠️ 常见注意事项
- 字段顺序:Jackson 按 Record 组件声明顺序进行序列化/反序列化,务必保持一致。
- 空构造函数:Record 没有无参构造器,反序列化要求 JSON 必须提供所有字段。如果某些字段可能缺失,请像上面那样提供带默认值的构造器。
- 内部类 Record:若 Record 定义在另一个类内部,确保是静态的(
static record),否则 Jackson 可能无法实例化。
💡 实际应用示例
假设一个 API 返回用户信息,Record 作为 DTO:
// 定义 Record
public record ApiResponse<T>(int code, String message, T data) {}
// 在 Controller 中使用 (Spring Boot 示例)
@GetMapping("/user/{id}")
public ApiResponse<Person> getUser(@PathVariable int id) {
Person user = new Person("王五", 30, "wangwu@example.com");
return new ApiResponse<>(200, "成功", user);
}
// 返回的 JSON 自动为: {"code":200,"message":"成功","data":{"name":"王五","age":30,"email":"wangwu@example.com"}}
Jackson 从 2.12 起对 Record 的自动化支持已经相当成熟,无需额外插件或复杂配置即可无缝集成到 Spring Boot 等框架中使用。
本文来自博客园,作者:ace--碳水化合物,转载请注明原文链接:https://www.cnblogs.com/ukzq/p/20043496

浙公网安备 33010602011771号