java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java Record 类

Java 记录类Record详解

作者:探索java

文章介绍了Java记录类的引入背景、语法特点及优势,包括自动生成构造器、访问器、equals、hashCode、toString等方法,强调其不可变性与类型安全性,适合用于数据封装,并通过示例和注意事项说明其使用场景与局限,感兴趣的朋友跟随小编一起看看吧

引言

在传统 Java 开发中,创建一个纯粹用于封装数据的类(如 DTO 或值对象)往往需要编写大量样板代码,包括构造器、getter、equals、hashCode 和 toString 方法。这些代码虽然重复,却难以避免,不仅影响开发效率,也降低了代码可读性。

为了解决这一问题,Java 在 JEP 359 中提出了“记录类”这一语言特性,并于 Java 14 首次以预览形式引入,在 Java 16 中正式发布。记录类的核心设计目标是为数据携带类提供一种简洁、可读性强且类型安全的声明方式。

本篇文章将带你深入理解记录类的原理与使用,掌握其各种高级特性,并通过丰富示例、性能对比和最佳实践指导,助你在实际项目中合理使用记录类,写出更加优雅、现代化的 Java 代码。

语法与基本用法

记录类的定义方式非常简单,使用 record 关键字声明,取代了传统类的冗长结构。基本语法如下:

public record Person(String name, int age) {
}

上述代码定义了一个 Person 记录类,包含两个组件(component):nameage,它们将被编译器自动提升为私有 final 字段,并生成相应的方法(稍后将详细介绍)。

与传统类相比,记录类具备以下特性:

示例:基本记录类定义

public record Book(String title, double price) {
}
public class Main {
    public static void main(String[] args) {
        Book book = new Book("Java 精通之路", 79.9);
        System.out.println(book.title());
        System.out.println(book);
    }
}

输出结果:

Java 精通之路
Book[title=Java 精通之路, price=79.9]

自动生成方法详解

记录类的核心价值之一是其自动生成的标准方法,这不仅减少了开发者的重复工作,也保证了行为的一致性和语义清晰性。以下是记录类在编译阶段自动生成的方法列表:

1. 访问器方法

每个组件都会生成一个访问器方法,其方法名与字段名一致。例如:

public record User(String username, String role) {
}
User user = new User("alice", "admin");
System.out.println(user.username()); // 输出:alice
System.out.println(user.role());     // 输出:admin

注意:记录类不生成传统的 getUsername() 形式的方法。

2. equals 和 hashCode 方法

记录类会基于字段值自动生成合理的 equalshashCode 方法。

User user1 = new User("alice", "admin");
User user2 = new User("alice", "admin");
System.out.println(user1.equals(user2)); // true
System.out.println(user1.hashCode() == user2.hashCode()); // true

比较是基于组件值进行的,而非引用。

3. toString 方法

记录类默认实现了符合逻辑的 toString() 方法,格式为:类名[字段1=值1, 字段2=值2]

System.out.println(user1.toString());
// 输出:User[username=alice, role=admin]

4. 规范构造器

编译器会生成一个公共构造器,其参数列表与组件顺序一致:

public User(String username, String role) {
    this.username = username;
    this.role = role;
}

此构造器不能省略字段赋值,且不能绕过不可变性。

不可变性的特性与价值

记录类的一个显著特性是:不可变性(Immutability)。一旦创建了记录类实例,其状态便不可更改,这使得记录类天然适合用于线程安全的数据传递、缓存键、日志记录和函数式编程等场景。

不可变性的实现方式

在记录类中:

public record Customer(String id, String name) {
    // 无法修改 id 和 name
}
Customer c1 = new Customer("001", "Alice");
c1.name = "Bob"; // 编译错误,字段是 final 的

不可变性的优势

与传统类的对比

在传统 Java 类中,需要人为地将字段设置为 private final,并避免提供 setter 方法,才能勉强模拟不可变性:

public final class ImmutablePerson {
    private final String name;
    private final int age;
    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}

记录类通过语法层级的约束,天然支持不可变性,避免了人为失误。

注意事项:深层不可变性

虽然记录类本身是不可变的,但如果其字段是可变对象(如 List、Map、数组),则需要谨慎处理。

public record UserProfile(String username, List<String> tags) {
}
List<String> list = new ArrayList<>();
list.add("java");
UserProfile profile = new UserProfile("bob", list);
profile.tags().add("record"); // 可变字段被修改

上述代码中,虽然 tags 字段本身是 final,但它引用的是一个可变列表,因此对象并不是真正不可变。

解决方案:

public record SafeUserProfile(String username, List<String> tags) {
    public SafeUserProfile {
        tags = List.copyOf(tags);
    }
}

这样就能保证 tags 无法在外部被修改,真正实现深层不可变。

构造器机制

尽管记录类自动为我们生成了标准构造器,但在实际开发中,仍有很多场景需要我们自定义构造逻辑,比如字段校验、数据转换等。Java 记录类支持三种构造器形式:

规范构造器(Canonical Constructor)

规范构造器是由编译器自动生成的构造函数,其参数列表与组件顺序一致,默认行为是将所有字段赋值:

public record Product(String name, double price) {
    // 编译器生成如下构造器:
    // public Product(String name, double price) {
    //     this.name = name;
    //     this.price = price;
    // }
}

我们也可以显式地定义规范构造器,来加入自定义逻辑,例如参数校验:

public record Product(String name, double price) {
    public Product {
        if (price < 0) {
            throw new IllegalArgumentException("价格不能为负数");
        }
    }
}

紧凑构造器(Compact Constructor)

Java 为记录类提供了一种语法糖,称为“紧凑构造器”。在紧凑构造器中,我们无需列出参数列表,编译器会自动将构造器参数与字段绑定。

public record Student(String name, int age) {
    public Student {
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负数");
        }
    }
}

等价于:

public record Student(String name, int age) {
    public Student(String name, int age) {
        if (age < 0) {
            throw new IllegalArgumentException("年龄不能为负数");
        }
        this.name = name;
        this.age = age;
    }
}

紧凑构造器简化了字段赋值过程,适用于多数校验逻辑场景。

自定义构造器与重载

记录类可以定义额外的构造器,但这些构造器必须调用规范构造器,不能绕过字段赋值流程:

public record Coordinate(int x, int y) {
    public Coordinate() {
        this(0, 0); // 默认构造
    }
    public Coordinate(int value) {
        this(value, value); // 重载构造
    }
}

注意:

通过构造器机制,记录类不仅保持了不可变性,还为开发者提供了足够的灵活性,用于字段校验、默认值填充、工厂构造等实际需求。

高级特性

记录类虽然语法简洁,但功能并不简单。它支持许多高级语言特性,包括静态成员、接口实现、泛型定义以及本地记录类的声明,使其具备在复杂应用中广泛使用的能力。

静态成员

记录类可以像普通类一样包含静态字段、静态方法和静态代码块,这些静态成员的行为与传统类完全一致。

public record Color(int red, int green, int blue) {
    public static final Color BLACK = new Color(0, 0, 0);
    public static final Color WHITE = new Color(255, 255, 255);
    public static String toHex(Color c) {
        return String.format("#%02x%02x%02x", c.red(), c.green(), c.blue());
    }
}
Color white = Color.WHITE;
System.out.println(Color.toHex(white)); // 输出:#ffffff

接口实现

记录类可以实现接口(包括函数式接口),并提供对应方法实现。

public interface Identifiable {
    String id();
}
public record Employee(String id, String name) implements Identifiable {
    // 自动实现 id() 方法
}

记录类实现接口时,可以通过组件直接满足接口方法签名,进一步提升了类型的表达力。

泛型支持

记录类可以定义为泛型类型,从而适用于多种数据类型的封装。

public record Pair<K, V>(K key, V value) {
}
Pair<String, Integer> entry = new Pair<>("age", 30);
System.out.println(entry.key());   // 输出:age
System.out.println(entry.value()); // 输出:30

泛型记录类尤其适用于通用值对象、键值对封装、元组等使用场景。

本地记录类(Local Records)

从 Java 16 开始,记录类也可以在方法内部定义,称为“本地记录类”,适用于封装局部方法逻辑中的中间数据结构。

public class ReportGenerator {
    public void generate() {
        record Summary(String title, int count) {}
        Summary summary = new Summary("周报", 12);
        System.out.println(summary);
    }
}

本地记录类只在方法范围内可见,能够提升临时数据处理的类型安全性与可读性,避免引入冗余的外部类定义。

通过这些高级特性,记录类在保持简洁的同时,依然具备高度灵活性,能够应对多种开发需求,适用于从简单 DTO 到泛型模型、工具类的广泛场景。

限制与注意事项

尽管记录类极大简化了不可变数据类的开发,但其设计也存在一些固有的限制,了解这些限制有助于合理使用,避免不合适的场景导致代码设计问题。

1. 继承限制

// 编译错误,记录类不能继承其他类 public record MyRecord extends SomeClass { } // 编译错误,记录类不能被继承 public class SubRecord extends MyRecord { }

2. 字段限制

3. 不支持无参构造器

4. 不支持可变字段

5. 限制扩展性

6. 序列化限制

理解并遵守这些限制,有助于避免误用记录类导致设计不佳和维护困难。

到此这篇关于Java 记录类Record详解的文章就介绍到这了,更多相关Java Record 类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文