java record

Java Record 是从 Java 14 开始预览,并在 Java 16 正式发布的特性。它是一种专门用于承载不可变数据的轻量级类,可以看作是一种“具名元组”。它的核心使命是简化数据载体类(Data Carrier)的编写,让你告别为 POJO 重复编写构造器、Getter、equalshashCodetoString 等样板代码的痛苦。

简单来说,以前写一个只有两个字段的类需要几十行代码,用 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 有几条硬性限制,以保证语义的纯粹性:

  1. 不可继承:Record 默认是 final 的,且无法继承其他类。因为它已隐式继承 java.lang.Record 父类。
  2. 所有字段都是 final:保证实例一旦创建就不可变。
  3. 不能有实例字段:除了在记录头中声明的组件(字段),不能再添加其他实例变量。

✅ 适用场景

当你需要创建如下类型的对象时,Record 是完美的选择:

  • DTO (数据传输对象):在应用层间传递数据。
  • VO (值对象):用于领域驱动设计,根据属性来识别身份。
  • API 的返回值:临时组合查询结果。
  • Map 的复合键:当键由多个值决定时,Record 能自动提供正确的 equalshashCode

📝 与其他概念的对比

特性 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;
    }
}

⚠️ 常见注意事项

  1. 字段顺序:Jackson 按 Record 组件声明顺序进行序列化/反序列化,务必保持一致。
  2. 空构造函数:Record 没有无参构造器,反序列化要求 JSON 必须提供所有字段。如果某些字段可能缺失,请像上面那样提供带默认值的构造器。
  3. 内部类 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 等框架中使用。

posted @ 2026-05-14 16:29  ace--碳水化合物  阅读(32)  评论(0)    收藏  举报