当大家在阅读JDK17之后的项目的源码时,经常会发现一个名为record的类型。这到底是什么类型?有什么作用?相信大家都会有这种疑问,所以我就想通过这篇文章来介绍一下record及其用法。
基本介绍
来看看JDK14官方文档对record的描述
Enhance the Java programming language with records. Records provide a compact syntax for declaring classes which are transparent holders for shallowly immutable data. This is a preview language feature in JDK 14.
从官方文档不难看出,record的出现就是为声明 持有不可变数据的类 提供了更简洁的语法,就是一个语法糖。可以一定程度解决Java冗杂的问题。使编写、阅读代码更加方便
主要特点
- record中所有的字段都是由final修饰的,即不可变
- 状态描述的每个组件都有一个公共的读取访问器方法,其名称和类型与组件相同;(类似于getter方法)
- 一个公共构造函数,其签名与状态描述相同,该构造函数从相应的参数初始化每个字段;
- equals 和 hashCode 的实现,规定如果两个记录属于同一类型且包含相同的状态,则它们相等;
- toString 的实现,其中包含所有记录组件的字符串表示及其名称。
record通过这些特点达到了简化代码的目的
假如我们不通过record实现一个Point类,实现起来是相当繁琐的
java
// 编译器生成的最终类结构(简化版)
final class Point extends java.lang.Record {
// 所有字段自动为 final
private final int x;
private final int y;
// 自动生成全参构造方法
public Point(int x, int y) {
this.x = x;
this.y = y;
}
// 自动生成与字段名同名的 getter 方法(无 get 前缀)
public int x() { return x; }
public int y() { return y; }
// 自动生成 equals(),基于所有字段的值比较
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
// 自动生成 hashCode(),基于所有字段的值计算
@Override
public int hashCode() {
return Objects.hash(x, y);
}
// 自动生成 toString(),包含所有字段名和值
@Override
public String toString() {
return "Point[x=" + x + ", y=" + y + "]";
}
}
如果我们通过record类型来描述Point类,则只需要一两行
arduino
record Point(int x, int y) {}
明显可以看出,这极大简化了开发。
与Lombok对比?
以上代码使用lombok的实现会是
java
import lombok.Value;
import lombok.NonNull;
@Value
public class Point {
// 校验参数非空(对于基本类型可改用手动校验)
int x;
int y;
}
}
lombok的优势在于兼容性好,jdk14之前的版本也能够使用
若追求更简洁的语法(如省略 get
前缀的 getter),或使用 Java 16+ 且无需兼容旧版本,record
会是更原生、更轻量的方案(无需依赖 Lombok)。
使用场景
record记录的就是不可变的数据,因此应用场景也围绕此展开
1. 数据传输对象(DTO,Data Transfer Object)
在分层架构(如 MVC、微服务)中,record
非常适合作为 DTO 传递数据。例如:
- 服务层与控制器层之间传递数据
- 微服务之间的接口数据交换
less
// 表示用户信息的 DTO
record UserDTO(Long id, String username, String email) {}
// 控制器中使用
@GetMapping("/users/{id}")
public UserDTO getUser(@PathVariable Long id) {
User user = userService.findById(id);
return new UserDTO(user.getId(), user.getUsername(), user.getEmail());
}
2. 方法返回多值结果
当方法需要返回多个相关值时,record
可以替代 Map
、Object[]
或自定义类,使返回值的类型和含义更清晰。
arduino
// 表示统计结果的 record
record Statistics(long total, long average, long max) {}
// 方法返回多值
public Statistics calculate(List<Long> numbers) {
long total = numbers.stream().mapToLong(n -> n).sum();
long average = total / numbers.size();
long max = numbers.stream().mapToLong(n -> n).max().orElse(0);
return new Statistics(total, average, max);
}
3. 数据库查询结果载体
在 ORM 映射或原生 SQL 查询中,record
可用于接收查询结果(尤其是多表关联查询的部分字段)。
less
// 接收订单和用户的关联查询结果
record OrderWithUser(Long orderId, String userName, LocalDateTime orderTime) {}
// JPA 中使用
@Query("SELECT new com.example.OrderWithUser(o.id, u.name, o.time) " +
"FROM Order o JOIN o.user u WHERE o.status = :status")
List<OrderWithUser> findOrdersWithUser(@Param("status") String status);
4. 临时数据容器
在处理中间计算结果、配置信息、元数据等场景中,record
可以快速定义临时数据结构。
arduino
// 表示配置项的键值对
record ConfigEntry(String key, String value, boolean required) {}
// 加载配置
List<ConfigEntry> configs = Arrays.asList(
new ConfigEntry("db.url", "jdbc:mysql://localhost", true),
new ConfigEntry("db.port", "3306", false)
);
5. 测试数据载体
在单元测试中,record
可用于定义测试用例的输入和预期输出,使测试数据更易读。
less
// 测试用例数据
record TestCase(String input, String expectedOutput) {}
// 测试方法
@Test
void testParser() {
List<TestCase> cases = Arrays.asList(
new TestCase("1+1", "2"),
new TestCase("3*4", "12")
);
for (TestCase tc : cases) {
assertEquals(tc.expectedOutput(), parser.parse(tc.input()));
}
}