在Java开发中,我们经常需要编写只用于承载数据的类,例如DTO、VO等。传统方式下,这类"数据载体类"需要大量样板代码:
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getters
public String name() { return name; }
public int age() { return age; }
// toString, equals, hashCode
@Override
public String toString() {
return "Person[name=" + name + ", age=" + age + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person)o;
return age == person.age && name.equals(person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
这种模式存在三大痛点:
代码冗长:简单数据类需要大量样板代码
维护困难:修改字段需要同步修改多个方法
意图模糊:类的设计意图被样板代码淹没
Java 14引入的记录类(Records)正是为了解决这些问题。
用一行代码替代传统类的所有样板代码:
public record Person(String name, int age) {}
编译器会自动生成:
私有final字段
规范构造器(canonical constructor)
访问器方法(name()和age())
toString(), equals(), hashCode()
记录类有明确的语义限制:
隐式final类,不能继承其他类
所有字段都是final的
不能声明实例字段(状态不可变)
自动生成的方法行为基于全部组件
记录类的核心设计哲学是"透明地建模数据":
Person p = new Person("Alice", 30);
System.out.println(p.name()); // Alice
System.out.println(p); // Person[name=Alice, age=30]
编译后的记录类会生成以下结构:
// 反编译后的近似代码
public final class Person extends java.lang.Record {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 自动生成的方法...
}
关键点:
继承自java.lang.Record抽象类
字段生成规则与声明顺序一致
equals/hashCode基于所有组件字段
记录类与Java模式匹配完美配合:
// instanceof模式匹配
if (obj instanceof Person p) {
System.out.println(p.name());
}
// switch模式匹配(JEP 406)
switch (obj) {
case Person(String name, int age) ->
System.out.println(name + ":" + age);
default -> throw new IllegalArgumentException();
}
可以覆盖规范构造器添加验证逻辑:
public record Person(String name, int age) {
public Person {
if (age < 0)
throw new IllegalArgumentException("Age cannot be negative");
name = name.trim(); // 参数处理
}
}
可以重写自动生成的方法:
public record Person(String name, int age) {
@Override
public String toString() {
return String.format("%s (%d years)", name, age);
}
}
可以添加静态字段和方法:
public record Person(String name, int age) {
public static final Person UNKNOWN = new Person("Unknown", 0);
public static Person newborn(String name) {
return new Person(name, 0);
}
}
记录类比传统类更节省内存:
没有额外的对象头开销
字段排列更紧凑
不可变性允许JVM优化
自动生成的方法都是final的,支持内联优化:
// 传统getter可能被重写,需要虚方法调用
public String getName() { return name; }
// 记录类访问器是final的,可直接内联
public final String name() { return name; }
记录类天然适合实现值对象:
public record Money(BigDecimal amount, Currency currency) {
public Money add(Money other) {
if (!currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(amount.add(other.amount), currency);
}
}
简化分层架构中的数据传递:
// API请求记录
public record RegisterRequest(
String username,
String email,
String password
) {}
// 领域值对象
public record UserId(String value) {
public UserId {
Objects.requireNonNull(value);
if (value.length() != 36) {
throw new IllegalArgumentException("Invalid UUID format");
}
}
}
记录类支持Java序列化,行为可预测:
public record Employee(Person person, Department department)
implements Serializable {}
// 序列化/反序列化保证对称性
支持在组件上使用注解:
public record User(
@NotBlank String username,
@Email String email,
@Size(min=8) String password
) {}
适合场景:
数据传输对象(DTO)
值对象(VO)
复合键类
方法返回多个值
命名约定:
使用名词命名(如Point、Range)
访问器方法不带get前缀(name()而非getName())
不适合场景:
需要继承的类
需要可变状态的类
需要精细控制equals行为的类
反模式示例:
// 错误:试图用记录类建模可变实体
public record Account(
String accountNumber,
BigDecimal balance
) {
// 违反记录类不可变原则!
public void deposit(BigDecimal amount) {
balance = balance.add(amount);
}
}
特性 | Java记录类 | Kotlin数据类 |
---|---|---|
可变性 | 完全不可变 | 默认不可变,可设为可变 |
继承 | 不能继承 | 可以继承 |
组件方法 | 自动生成 | 需显式声明(data修饰符) |
解构声明 | 通过模式匹配 | 内置支持 |
扩展函数 | 不支持 | 支持 |
默认参数 | 不支持 | 支持 |
可能加入的特性:
支持解构模式
允许声明非final记录类
更灵活的模式匹配支持
生态系统整合:
JPA实体支持
更多框架的深度集成
序列化库的优化支持
Java记录类通过声明式语法大幅简化了数据载体类的编写,使开发者能够更专注于数据建模本身而非样板代码。它的不可变特性和透明数据语义使其成为现代Java开发中建模数据传输对象、值对象等场景的理想选择。虽然在某些灵活性方面有所限制,但正是这些约束保证了更好的设计一致性和运行时性能。
记录类代表了Java语言向更简洁、更声明式编程风格的演进,是Java现代化进程中的重要里程碑。对于开发者而言,合理运用记录类可以显著提高代码的可读性和可维护性,同时减少潜在的错误。