84. Java Record 类型的使用与解析

175 阅读3分钟

[toc]

84. Java Record 类型的使用与解析

1. 引言

在 Java 中,我们经常需要创建不可变的数据模型(immutable data model)。过去,最常见的方式是使用 final 关键字修饰类和字段,并提供构造方法进行初始化。这种方式虽然可靠,但需要手动编写大量重复的代码,例如 toString()equals()hashCode() 方法,甚至还可能需要实现 Serializable 接口来支持序列化。这种方式不仅繁琐,而且容易出错。

Java 14 开始,JDK 引入了 record 关键字,使得创建不可变类变得更加简单高效。接下来,我们将详细探讨 record 的用法,并通过示例帮助大家更好地理解。


2. 传统方式创建不可变类

示例:使用普通类创建 Point

public final class Point {
    private final int x;
    private final int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
    
    @Override
    public String toString() {
        return "Point{" + "x=" + x + ", y=" + y + '}';
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Point point = (Point) obj;
        return x == point.x && y == point.y;
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

存在的问题

  1. 需要手动编写构造方法、访问器(getter)、toString()equals()hashCode() 方法。
  2. 如果修改类的字段,如增加 z 轴,需要手动更新 equals()hashCode(),容易出错。
  3. 代码冗长,影响可读性和维护性。

3. 使用 Record 简化代码

Java 14 及以上版本,我们可以使用 record 关键字大幅简化上述代码。

image.png

示例:使用 Record 创建 Point

public record Point(int x, int y) {}

这行代码就完成了所有必要的工作!

Record 生成的内容

  • Point 类是 final 的,不能被继承。
  • xy 作为 private final 字段存在。
  • 自动生成 x()y() 方法,代替 getX()getY()
  • 自动生成 toString()equals()hashCode() 方法。
  • record 还可以自动支持 Serializable

示例:调用 Record 方法

public class Main {
    public static void main(String[] args) {
        Point p1 = new Point(3, 5);
        System.out.println(p1); // 输出:Point[x=3, y=5]
        
        Point p2 = new Point(3, 5);
        System.out.println(p1.equals(p2)); // 输出:true
        
        System.out.println(p1.hashCode()); // 自动生成哈希码
    }
}

4. Record 的构造器

4.1 默认构造器

record 默认提供一个全参数构造器:

public record Point(int x, int y) {}
// 等价于:
public Point(int x, int y) {
    this.x = x;
    this.y = y;
}

4.2 自定义构造器(紧凑构造器)

如果需要对参数进行校验,可以使用紧凑构造器(compact constructor):

public record Range(int start, int end) {
    public Range {
        if (end <= start) {
            throw new IllegalArgumentException("end 必须大于 start");
        }
    }
}

示例:

Range r1 = new Range(5, 10); // 正常创建
Range r2 = new Range(10, 5); // 抛出异常

4.3 标准构造器

如果不想使用紧凑构造器,也可以显式定义构造方法:

public record Range(int start, int end) {
    public Range(int start, int end) {
        if (end <= start) {
            throw new IllegalArgumentException("end 必须大于 start");
        }
        this.start = start;
        this.end = end;
    }
}

5. Record 的限制

  1. 不能添加额外的实例字段

    • 例如,以下代码会报错:
    public record Point(int x, int y) {
        private int z; // ❌ 错误,record 不能有额外的实例字段
    }
    
  2. 不能定义字段初始值

    • 例如,以下代码是非法的:
    public record Point(int x, int y) {
        private final int z = 10; // ❌ 不能手动初始化字段
    }
    
  3. 不能有实例初始化块

    • 例如,以下代码是非法的:
    public record Point(int x, int y) {
        { System.out.println("初始化"); } // ❌ record 不能有实例初始化块
    }
    
  • 可以定义静态字段和静态初始化块
public record Config(String name) {
    static String DEFAULT_NAME = "Unknown";
    static {
        System.out.println("Config 类已加载");
    }
}

🎯 record 适用场景

  • 数据传输对象(DTO
  • 不可变数据建模(如 PointColor)。
  • 简单的键值映射

6. 总结

  1. recordJava 14 引入的新特性,用于简化不可变类的创建。
  2. record 自动生成构造器、访问器、toString()equals()hashCode()
  3. recordfinal 的,不能被继承,但可以实现接口。
  4. 可以使用紧凑构造器进行参数校验。
  5. record 不能有额外实例字段,但可以有静态字段。