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 提高代码可读性和业务逻辑清晰度
相关推荐
逾非时4 分钟前
python网络爬虫的基本使用
开发语言·爬虫·python
ppdkx7 分钟前
python训练营第33天
开发语言·python
攻城狮7号24 分钟前
Java三十而立:Java 的30岁与Spring AI 1.0正式发布
java·人工智能·深度学习·ai·spring ai
Su米苏25 分钟前
Spring Boot 中修改 HTTP 响应状态码(即 `response.status`)可以通过以下几种方式实现
java
玉笥寻珍32 分钟前
从零开始:Python语言进阶之异常处理
开发语言·python
Java永无止境34 分钟前
JavaSE常用API之Runtime类:掌控JVM运行时环境
java·开发语言·jvm
caihuayuan535 分钟前
Vue3 Composition API: 企业级应用最佳实践方案
java·大数据·spring boot·后端·课程设计
只在空想家37 分钟前
SpringBoot JAR 启动原理
java·spring boot·后端·jar
龙湾开发41 分钟前
C++ vscode配置c++开发环境
开发语言·c++·笔记·vscode·学习