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 提高代码可读性和业务逻辑清晰度
相关推荐
Javatutouhouduan6 小时前
2026Java面试的正确打开方式!
java·高并发·java面试·java面试题·后端开发·java编程·java八股文
chao1898446 小时前
基于 SPEA2 的多目标优化算法 MATLAB 实现
开发语言·算法·matlab
JAVA面经实录9176 小时前
Java初级最终完整版学习路线图
java·spring·eclipse·maven
赏金术士6 小时前
Kotlin 习题集 · 高级篇
android·开发语言·kotlin
Cat_Rocky7 小时前
k8s-持久化存储,粗浅学习
java·学习·kubernetes
楼兰公子7 小时前
buildroot 在编译rust时裁剪平台类型数量的方法
开发语言·后端·rust
知识领航员8 小时前
蘑兔AI音乐深度实测:功能拆解、实测表现与适用场景
java·c语言·c++·人工智能·python·算法·github
吴声子夜歌8 小时前
Go——并发编程
开发语言·后端·golang
释怀°Believe8 小时前
Spring解析
java·后端·spring
ooseabiscuit8 小时前
Laravel4.x:现代PHP框架的奠基之作
java·开发语言·php