部分替代Lombok?不可变数据的载体?一篇文章带你了解JDK16正式引用的record类型!

当大家在阅读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 可以替代 MapObject[] 或自定义类,使返回值的类型和含义更清晰。

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()));
    }
}

相关推荐
用户0332126663676 小时前
Java 高效处理 Word 文档:查找并替换文本的全面指南
java
轮到我狗叫了6 小时前
力扣.1054距离相等的条形码力扣767.重构字符串力扣47.全排列II力扣980.不同路径III力扣509.斐波那契数列(记忆化搜索)
java·算法·leetcode
渣哥6 小时前
你遇到过 ConcurrentModificationException 吗?其实很常见
java
lunzi_fly6 小时前
【源码解读之 Mybatis】【基础篇】-- 第1篇:MyBatis 整体架构设计
java·mybatis
JIngJaneIL7 小时前
汽车租赁|基于Java+vue的汽车租赁系统(源码+数据库+文档)
java·vue.js·spring boot·汽车·论文·毕设·汽车租赁系统
渣哥7 小时前
有一天,我和 CopyOnWriteArrayList 杯“线程安全”的咖啡
java
叽哥7 小时前
Kotlin学习第 3 课:Kotlin 流程控制:掌握逻辑分支与循环的艺术
android·java·kotlin
杨杨杨大侠7 小时前
第5章:实现Spring Boot集成
java·github·eventbus
华仔啊7 小时前
工作5年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!
java·后端