纪元117:罗马和平中文试玩版
42.7G · 2025-09-10
当大家在阅读JDK17之后的项目的源码时,经常会发现一个名为record的类型。这到底是什么类型?有什么作用?相信大家都会有这种疑问,所以我就想通过这篇文章来介绍一下record及其用法。
来看看JDK14官方文档对record的描述
从官方文档不难看出,record的出现就是为声明 持有不可变数据的类 提供了更简洁的语法,就是一个语法糖。可以一定程度解决Java冗杂的问题。使编写、阅读代码更加方便
record通过这些特点达到了简化代码的目的
假如我们不通过record实现一个Point类,实现起来是相当繁琐的
// 编译器生成的最终类结构(简化版)
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类,则只需要一两行
record Point(int x, int y) {}
明显可以看出,这极大简化了开发。
以上代码使用lombok的实现会是
import lombok.Value;
import lombok.NonNull;
@Value
public class Point {
// 校验参数非空(对于基本类型可改用手动校验)
int x;
int y;
}
}
lombok的优势在于兼容性好,jdk14之前的版本也能够使用
若追求更简洁的语法(如省略 get
前缀的 getter),或使用 Java 16+ 且无需兼容旧版本,record
会是更原生、更轻量的方案(无需依赖 Lombok)。
record记录的就是不可变的数据,因此应用场景也围绕此展开
在分层架构(如 MVC、微服务)中,record
非常适合作为 DTO 传递数据。例如:
// 表示用户信息的 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());
}
当方法需要返回多个相关值时,record
可以替代 Map
、Object[]
或自定义类,使返回值的类型和含义更清晰。
// 表示统计结果的 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);
}
在 ORM 映射或原生 SQL 查询中,record
可用于接收查询结果(尤其是多表关联查询的部分字段)。
// 接收订单和用户的关联查询结果
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);
在处理中间计算结果、配置信息、元数据等场景中,record
可以快速定义临时数据结构。
// 表示配置项的键值对
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)
);
在单元测试中,record
可用于定义测试用例的输入和预期输出,使测试数据更易读。
// 测试用例数据
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()));
}
}