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 提高代码可读性和业务逻辑清晰度
相关推荐
冰暮流星几秒前
javascript之String方法介绍
开发语言·javascript·ecmascript
nianniannnn22 分钟前
Qt QMessageBox知识点
开发语言·数据库·qt
故乡de云1 小时前
AWS 添加付款方式失败排查清单:卡片、银行风控与账号状态逐项定位
开发语言·php
其美杰布-富贵-李1 小时前
Spring Boot 依赖注入说明文档
java·spring boot·python
J_yyy1 小时前
【Qt核心组件类框架】
开发语言·qt
tachibana21 小时前
hot100 数组中的第K个最大元素(215)
java·数据结构·算法·leetcode
our_times1 小时前
2026年Java开发者破局指南:Spring AI 2.0 与 Agent 开发实战
java·人工智能·spring
运维行者_1 小时前
如何查看每个IP的带宽使用情况?NetFlow 技术实战指南
开发语言·网络·分布式·后端·架构·带宽
糖果店的幽灵1 小时前
langgraph的分支结构之 - 静态分支详解
java·服务器·数据库·langgraph
天天进步20151 小时前
Python全栈项目--基于深度学习的图像超分辨率系统
开发语言·python·深度学习