Record 记录类

Java 16 引入了 Record,用来简化不可变数据类的写法。以前写一个简单的数据类,需要手写一堆 getter、equals、hashCode、toString...

// 以前:POJO 模板代码
public class User {
    private final String name;
    private final int age;
    
    public User(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) { /* ... */ }
    @Override
    public int hashCode() { /* ... */ }
    @Override
    public String toString() { /* ... */ }
}

用 Record,一行搞定:

// 现在:用 Record
public record User(String name, int age) {}

今天我们来把 Record 彻底讲透。

一、Record 的基本语法

1.1 声明 Record

// 声明一个 Point record
public record Point(int x, int y) {}

// 使用
Point p = new Point(1, 2);
int x = p.x();      // getter 叫 x(),不是 getX()
int y = p.y();

1.2 自动生成的内容

声明 record Point(int x, int y) 后,编译器自动生成:

组件说明
构造方法Point(int x, int y)
getterx()y()
equals比较所有字段
hashCode基于所有字段
toString显示所有字段
static 方法自动生成

1.3 equals、hashCode、toString 示例

record User(String name, int age) {}

User u1 = new User("张三", 25);
User u2 = new User("张三", 25);

u1.equals(u2);           // true(内容相同)
u1.hashCode() == u2.hashCode();  // true
u1.toString();           // User[name=张三, age=25]

二、Compact Constructor

2.1 什么是 Compact Constructor

如果需要在构造方法中做验证或转换,可以使用 compact constructor:

public record User(String name, int age) {
    // Compact Constructor:不用写参数
    public User {
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负");
        }
        // 可以在这里做转换
        if (name != null) {
            name = name.trim();
        }
    }
}

2.2 完整构造方法 vs Compact

// 完整构造方法(显式参数)
public record User(String name, int age) {
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// Compact 构造方法(隐式参数)
public record User(String name, int age) {
    public User {
        // 直接用 name 和 age,编译器帮你传参
        Objects.requireNonNull(name);
    }
}

三、Record 的高级特性

3.1 添加方法

public record Point(int x, int y) {
    // 可以添加实例方法
    public double distanceFromOrigin() {
        return Math.sqrt(x * x + y * y);
    }
    
    // 可以添加静态方法
    public static Point origin() {
        return new Point(0, 0);
    }
}

Point p = new Point(3, 4);
double dist = p.distanceFromOrigin();  // 5.0
Point origin = Point.origin();

3.2 实现接口

public interface Mappable {
    String toJson();
}

public record User(String name, int age) implements Mappable {
    @Override
    public String toJson() {
        return """
                {"name": "%s", "age": %d}
                """.formatted(name, age);
    }
}

3.3 添加静态字段

public record Color(int r, int g, int b) {
    // 可以添加静态字段
    public static Color RED = new Color(255, 0, 0);
    public static Color GREEN = new Color(0, 255, 0);
    public static Color BLUE = new Color(0, 0, 255);
    
    // 静态方法
    public static Color fromHex(String hex) {
        // 实现十六进制转 RGB
        int rgb = Integer.parseInt(hex.substring(1), 16);
        return new Color((rgb >> 16) & 0xFF, 
                         (rgb >> 8) & 0xFF, 
                         rgb & 0xFF);
    }
}

3.4 本地 Record

Record 可以写在方法内部:

public void process() {
    // 本地 Record,只在这个方法里用
    record Result(int code, String message) {}
    
    Result r = new Result(200, "OK");
    System.out.println(r.code());
}

3.5 Record 作为模式匹配

// Java 16+:instanceof 模式匹配
Object obj = new User("张三", 25);

if (obj instanceof User(String name, int age)) {
    System.out.println(name + " is " + age + " years old");
}

// Java 21+:switch 模式匹配
String describe(Object obj) {
    return switch (obj) {
        case null -> "null";
        case Point(int x, int y) -> "Point at (" + x + ", " + y + ")";
        case User(String name, int age) -> "User " + name;
        default -> "Unknown";
    };
}

四、【直观类比】

【直观类比】

Record 就像一张"卡片":

┌─────────────────────────┐
│         User            │
│  ┌───────────────────┐  │
│  │ name: 张三         │  │
│  │ age: 25           │  │
│  └───────────────────┘  │
│                         │
│  toString() ✅          │
│  equals() ✅           │
│  hashCode() ✅         │
│  getter ✅             │
└─────────────────────────┘

Record = 数据 + 样板代码(自动生成)

五、Record vs 传统类 vs Lombok

5.1 对比表

特性Record传统类Lombok @Data
不可变✅ 天然不可变可变可变
样板代码✅ 自动生成❌ 手动写✅ 自动生成
getter 命名field()getField()getField()
编译时语言内置原生注解处理器
可继承❌ 不能✅ 可以✅ 可以

5.2 为什么 Record 是不可变的

record User(String name, int age) {}

User u = new User("张三", 25);
u.name = "李四";  // ❌ 编译错误!没有 setter
u.age = 30;      // ❌ 编译错误!

Record 的所有字段都是隐式 final 的。

5.3 Record 不支持继承

// ❌ 编译错误
record Child(String name, int age, String school) extends User {}

// Record 不能继承任何类
// Record 也不能被继承

但 Record 可以实现接口:

// ✅ 可以
record User(String name, int age) implements Serializable {}

六、生产避坑

6.1 ❌ 错误示范:Record 用于可变对象

// ❌ 不适合用 Record
record MutableList(List<String> items) {
    public void add(String item) {  // ❌ 虽然能编译,但违背了不可变原则
        items.add(item);
    }
}

// ✅ 应该用普通类,或者
record User(String name, int age) {
    public User withAge(int newAge) {
        return new User(name, newAge);  // 返回新实例
    }
}

6.2 ❌ 错误示范:Record 有太多职责

// ❌ Record 应该只做数据传输,不应该有复杂业务逻辑
record BadExample(String data) {
    public void processData() {  // 不推荐
        // 复杂业务逻辑
    }
}

// ✅ Record 职责单一
record GoodExample(String name, int age) {}

6.3 ✅ 正确示范:用 Record 做 DTO

// 完美的 DTO
public record UserDTO(
    Long id,
    String name,
    String email,
    LocalDateTime createdAt
) implements Serializable {}

// API 响应
public record ApiResponse<T>(
    int code,
    String message,
    T data
) {}

七、面试追问链

第一层:基础概念

面试官问:"Record 是什么?"

Java 16 引入的语法糖,用来简化不可变数据类的写法。声明 record Point(int x, int y) 后,编译器自动生成构造方法、getter、equals、hashCode、toString。

第二层:特性

面试官问:"Record 和普通类有什么区别?"

Record 是隐式 final 的,所有字段都是 final 的,不能继承其他类但可以实现接口。可以添加方法、静态字段和静态方法。Compact constructor 可以在构造时做验证。

第三层:应用场景

面试官问:"什么时候用 Record?"

适合作为 DTO、API 响应、配置对象等纯数据传输对象。不适合有复杂业务逻辑或需要继承的场景。

【学习小结】

  • Record 用 record 关键字声明
  • 自动生成 constructor、getter、equals、hashCode、toString
  • 不可变:字段是 final 的
  • 不能继承类,可以实现接口
  • 适合做 DTO、API 响应、配置对象
  • 支持模式匹配(Java 16+)