85. Java Record 深入解析:构造函数、访问器、序列化与实际应用

文章目录

85. Java Record 深入解析:构造函数、访问器、序列化与实际应用


第一部分:构造函数(Constructor)

1. 记录的构造函数定义

大家好,今天我们来深入学习 Java 的 Record(记录)类型。🚀

在 Java 中,Record是一种特殊的类 ,主要用于存储数据。每个Record都会自动生成构造函数、toString()equals()hashCode() 方法,使其非常适合用作数据载体。

我们可以为一个 Record 自定义构造函数,只要这个构造函数调用了该Record主构造函数(canonical constructor)。

来看看下面的例子:

java 复制代码
public record State(String name, String capitalCity, List<String> cities) {
    
    // 紧凑构造函数(Compact Constructor)
    public State {
        cities = List.copyOf(cities); // 创建不可变列表,防止外部修改
    }
    
    // 无城市列表的构造函数
    public State(String name, String capitalCity) {
        this(name, capitalCity, List.of());
    }
    
    // 变长参数的构造函数
    public State(String name, String capitalCity, String... cities) {
        this(name, capitalCity, List.of(cities));
    }
}

🔹 这里的关键点

  • 紧凑构造函数 (Compact Constructor)不需要显式列出参数,它的作用是在构造过程中做一些额外处理,比如这里的 List.copyOf(cities),确保 cities 不可变。
  • 重载构造函数 :我们定义了两个额外的构造函数,一个用于不包含 cities 的情况,另一个允许使用 可变参数(varargs) 传递城市名称。
  • this() 调用 :在 Java 中,构造函数的第一行 必须调用同一个类的另一个构造函数或 super()

💡 示例应用场景 : 假设你的程序中有一个 State 对象,它存储了某个州的名字、首府,以及一些城市信息。你希望:

  1. 确保城市列表不会在外部被修改。
  2. 允许用户创建不含城市列表State 对象。
  3. 允许用户直接传入多个城市名称,而不是自己创建 List<String>

这时,多个构造函数的设计就能满足不同的需求!


第二部分:访问器(Accessor)

2.1 记录的默认访问器

💡 大家还记得吗? 记录类型会自动生成访问器 (accessor),即字段名相同的方法

比如:

java 复制代码
public record Point(int x, int y) {}

这个 Point 记录会自动生成两个方法:

java 复制代码
public int x() { return x; }
public int y() { return y; }

这样,我们可以直接调用 point.x()point.y() 获取值。

2.2 自定义访问器

有时候,默认的访问器不够用,比如 State 记录中,如果没有在构造函数中做防御性拷贝,我们可以在访问器中返回一个不可变副本:

java 复制代码
public List<String> cities() {
    return List.copyOf(cities);
}

这样,每次访问 cities() 时都会返回一个不可变的列表,避免外部修改原始数据。


第三部分:记录的序列化(Serialization)

如果需要让 Record 支持序列化 (Serialization),只需实现 Serializable 接口:

java 复制代码
public record State(String name, String capitalCity, List<String> cities) implements Serializable {}

但是,要注意

  1. 不能使用 writeObject()readObject() 方法自定义序列化行为。
  2. 不能实现 Externalizable
  3. 反序列化时,始终会调用 主构造函数(Canonical Constructor),确保所有的校验逻辑都会被执行。

💡 示例场景 : 如果你的应用程序需要在不同服务之间传输 State 对象,使用 Record 可以确保数据一致性,防止意外修改。


第四部分:实际应用案例(Use Case)

4.1 计算拥有最多城市的州

假设我们有一个 City 记录和 Zhou 记录:

java 复制代码
public record City(String name, Zhou state) {}
public record Zhou(String name) {}

我们有一个 List<City>,需要找到拥有最多城市的州

第一步:构建(Histogram)
java 复制代码
List<City> cities = List.of(
    new City("New York", new Zhou("NY")),
    new City("Los Angeles", new Zhou("CA")),
    new City("San Francisco", new Zhou("CA"))
);

Map<Zhou, Long> numberOfCitiesPerState =
    cities.stream()
          .collect(Collectors.groupingBy(
                   City::state, Collectors.counting()
          ));
 System.out.println(numberOfCitiesPerState);

这里,我们用 Collectors.groupingBy() 来计算每个州的城市数量

1. 初始化空列表
java 复制代码
List<City> cities = List.of();
  • 作用 :创建一个不可变的空列表,类型为 City
  • 特点:
    • Java 9+ 支持的工厂方法,语法简洁。
    • 列表一旦创建,无法添加或删除元素(不可变)。

2. 流操作与分组统计
java 复制代码
Map<Zhou, Long> numberOfCitiesPerState =
    cities.stream()
          .collect(Collectors.groupingBy(
                   City::state, Collectors.counting()
          ));
  • 流程分解
    1. cities.stream():将列表转换为流(Stream),以便进行链式操作。
    2. collect(Collectors.groupingBy(...)):使用收集器对流元素进行分组统计。
      • City::state :分组依据为 City 对象的 state 属性(即州)。
      • Collectors.counting():统计每个分组的元素数量。
  • 结果
    • 返回一个 Map<Zhou, Long>,键是州对象,值是该州的城市数量。
    • 示例 :若有 3 个城市属于 "California" 州,则 Map 中会有键值对 California → 3L
第二步:找到最多城市的州
java 复制代码
Map.Entry<State, Long> stateWithTheMostCities =
    numberOfCitiesPerState.entrySet().stream()
                          .max(Map.Entry.comparingByValue())
                          .orElseThrow();

但这样写可读性不高 ,因为 Map.Entry<State, Long> 只是个键值对,缺乏语义信息

第三步:使用 Record 提升可读性

我们可以定义一个新的 Record,表示 "某个州及其城市数量"

java 复制代码
record NumberOfCitiesPerState(Zhou state, long numberOfCities) {
    public NumberOfCitiesPerState(Map.Entry<State, Long> entry) {
        this(entry.getKey(), entry.getValue());
    }
    
    public static Comparator<NumberOfCitiesPerState> comparingByNumberOfCities() {
        return Comparator.comparing(NumberOfCitiesPerState::numberOfCities);
    }
}

然后,优化 max() 代码,使其更具可读性:

java 复制代码
NumberOfCitiesPerState stateWithTheMostCities =
    numberOfCitiesPerState.entrySet().stream()
                          .map(NumberOfCitiesPerState::new)
                          .max(NumberOfCitiesPerState.comparingByNumberOfCities())
                          .orElseThrow();

优势

  • 代码更加清晰,NumberOfCitiesPerState 让业务逻辑一目了然。
  • 避免直接操作 Map.Entry,提高可读性和可维护性。

总结

  1. 记录类(Record) 适用于不可变数据模型。
  2. 自定义构造函数 可增强安全性,如防御性拷贝。
  3. 访问器方法 可定制逻辑,增强封装。
  4. 序列化时,主构造函数始终被调用,保证一致性。
  5. 使用 Record 提高代码可读性和业务逻辑清晰度
相关推荐
Flittly16 小时前
【AgentScope Java新手村系列】(16)从RAG到多路检索
java·spring boot·spring
小兔崽子去哪了16 小时前
Java 生成二维码解决方案
java·后端
人活一口气20 小时前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
NE_STOP1 天前
Vibe Coding -- 完整项目案例实操
java
荣码1 天前
GraphRAG:普通RAG只能回答"点"的问题,我踩了4个坑才搞懂
java·python
SimonKing1 天前
Google第三方授权登录
java·后端·程序员
明月光8181 天前
从一行 @Builder 说起:重新拾起 Java 的 Lombok、注解与 Builder 模式
java
考虑考虑1 天前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯1 天前
GoF设计模式——中介者模式
java·后端·spring·设计模式
青石路1 天前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java