Java 16关键字之record:精简的不可变数据类
Java 16关键字之record:精简的不可变数据类
引言
在 Java 开发中,我们经常需要编写一些仅用于存储数据的类,比如 DTO(数据传输对象)、值对象或实体。这些类通常包含私有字段、Getter/Setter、equals()、hashCode()、toString() 和构造函数,导致大量的样板代码。虽然 IDE 可以自动生成这些代码,但维护起来依然繁琐。
为了解决这个问题,Java 14 引入了 Record 作为预览特性,并在 Java 16 中正式成为标准功能。Record 是一种全新的类型,专门用于声明不可变的数据载体。
背景
在 Java 14 之前,定义一个简单的“数据持有者”类需要编写大量重复代码。例如
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 手动实现 getter、toString、equals、hashCode
public String getName() { return name; }
public int getAge() { return age; }
@Override
public String toString() { return "Person[name=" + name + ", age=" + age + "]"; }
@Override
public boolean equals(Object o) { /* ... */ }
@Override
public int hashCode() { /* ... */ }
}
这种模式代码冗长、可读性差。因而催生出record,,解决这些问题,自动处理数据类的样板代码。
record语法和原理
record类的定义方式非常简单,基本语法如下:
public record Person(String name, int age) {}
编译器在编译时会自动生成以下内容:
- 所有字段:自动声明为
private final; - 构造函数:自动生成带参数的构造器(无其他构造方法);
- 核心方法:自动生成 toString()、equals()、hashCode();
- 访问器:自动生成字段的 getter 方法(如 name()),注意
方法名与组件名相同,没有 get 前缀。
关键机制:
record隐式继承java.lang.Record,不可继承其他类;- 字段是只读的,编译器强制不可变性;
- 生成的代码基于字段顺序(如 Person(“Alice”, 30) 的 name 和 age 顺序)。
使用方法
1. 基础定义和使用
public record Person(String name, int age) { }
// 使用
Person p = new Person("Alice", 30);
System.out.println(p.name()); // 访问器,注意不是 getName()
System.out.println(p.age());
System.out.println(p); // 自动生成的 toString:Person[name=Alice, age=30]
2. 自定义方法
record 允许添加静态方法或实例方法(但不能添加额外字段或覆盖 equals/hashCode)
如:
public record Person(String name, int age) {
public int birthYear() {
return LocalDate.now().getYear() - age;
}
public static Person createAdult(String name) {
return new Person(name, 18);
}
}
// 使用
Person alice = Person.of("Alice", 30);
System.out.println(alice.birthYear());
3. 紧凑构造方法
如果你需要在构造时进行校验或规范化,可以定义紧凑构造方法,它没有参数列表,直接在方法体中编写逻辑。如:
public record Person(String name, int age) {
public Person {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("姓名不能为空");
}
// 可以在此处对参数进行修改,比如统一格式化
name = name.trim();
}
}
紧凑构造方法会在标准构造方法之前执行,并且参数可以被修改(修改后会反映到字段中)。注意,紧凑构造方法只能用于校验和调整参数,不能为字段重新赋值(因为字段是 final 的,赋值由编译器生成的标准构造完成)。
4. 搭配注解使用
Record 的组件可以添加注解,这些注解会传播到对应的字段、访问器方法和构造参数上,适用于许多框架(如 JSON 序列化、验证框架)。如:
import jakarta.validation.constraints.*;
public record User(
@NotNull String username,
@Min(18) int age
) {
}
5. 本地 Record
Record 也可以声明为局部类(在方法内部),这在某些场景下非常实用。如:
public List<String> getNames() {
record NameEntry(String firstName, String lastName) {}
return list.stream()
.map(e -> new NameEntry(e.getFirstName(), e.getLastName()))
.map(NameEntry::toString)
.collect(Collectors.toList());
}
使用注意事项
1. 不可变性
Record 被设计为不可变类,所有字段都是 final 的。这意味着一旦创建,其内部状态不能改变。也不能添加可变字段。
2. 不能继承或扩展其他类
record隐式继承java.lang.Record,不能显式继承其他类;Record不能声明实例字段(除了组件对应的字段),只能声明静态字段;Record不能定义实例初始化块。
3. 不能有自定义构造函数
编译器只生成带参数的构造器,不能添加其他构造方法。
public record Person(String name, int age) {
public Person(String name) { // 编译错误
this(name, 0);
}
}
4. 不能覆盖 equals/hashCode/toString
这些方法由编译器自动生成,禁止手动重写。
5. 序列化
Record 支持序列化,但序列化行为与传统 Java 对象略有不同。Record 的序列化会使用规范构造方法重建对象,而不是通过反射设置字段。这意味着即使你修改了 Record 的字段名(但保留组件名),反序列化也能正常工作。但需要注意,Record 的序列化版本号(serialVersionUID) 是根据组件和类型自动生成的,一般无需显式声明。
更多推荐




所有评论(0)