文章目录
- 85. Java Record 深入解析:构造函数、访问器、序列化与实际应用
- **第一部分:构造函数(Constructor)**
- **1. 记录的构造函数定义**
- **第二部分:访问器(Accessor)**
- **2.1 记录的默认访问器**
- **2.2 自定义访问器**
- **第三部分:记录的序列化(Serialization)**
- **第四部分:实际应用案例(Use Case)**
- **4.1 计算拥有最多城市的州**
- **第一步:构建(Histogram)**
- 1. 初始化空列表
- 2. 流操作与分组统计
- **第二步:找到最多城市的州**
- **第三步:使用 `Record` 提升可读性**
85. Java Record 深入解析:构造函数、访问器、序列化与实际应用
第一部分:构造函数(Constructor)
1. 记录的构造函数定义
大家好,今天我们来深入学习 Java 的 Record
(记录)类型。🚀
在 Java 中,Record
是一种特殊的类,主要用于存储数据。每个Record
都会自动生成构造函数、toString()
、equals()
、hashCode()
方法,使其非常适合用作数据载体。
我们可以为一个 Record
自定义构造函数,只要这个构造函数调用了该Record
的主构造函数(canonical constructor)。
来看看下面的例子:
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
对象,它存储了某个州的名字、首府,以及一些城市信息。你希望:
- 确保城市列表不会在外部被修改。
- 允许用户创建不含城市列表的
State
对象。 - 允许用户直接传入多个城市名称,而不是自己创建
List<String>
。
这时,多个构造函数的设计就能满足不同的需求! ✅
第二部分:访问器(Accessor)
2.1 记录的默认访问器
💡 大家还记得吗? 记录类型会自动生成访问器(accessor),即字段名相同的方法。
比如:
public record Point(int x, int y) {}
这个 Point
记录会自动生成两个方法:
public int x() { return x; }
public int y() { return y; }
这样,我们可以直接调用 point.x()
和 point.y()
获取值。
2.2 自定义访问器
有时候,默认的访问器不够用,比如 State
记录中,如果没有在构造函数中做防御性拷贝,我们可以在访问器中返回一个不可变副本:
public List<String> cities() {return List.copyOf(cities);
}
这样,每次访问 cities()
时都会返回一个不可变的列表,避免外部修改原始数据。
第三部分:记录的序列化(Serialization)
如果需要让 Record
支持序列化(Serialization),只需实现 Serializable
接口:
public record State(String name, String capitalCity, List<String> cities) implements Serializable {}
但是,要注意:
- 不能使用
writeObject()
和readObject()
方法自定义序列化行为。 - 不能实现
Externalizable
。 - 反序列化时,始终会调用 主构造函数(Canonical Constructor),确保所有的校验逻辑都会被执行。
💡 示例场景: 如果你的应用程序需要在不同服务之间传输 State
对象,使用 Record
可以确保数据一致性,防止意外修改。
第四部分:实际应用案例(Use Case)
4.1 计算拥有最多城市的州
假设我们有一个 City
记录和 Zhou
记录:
public record City(String name, Zhou state) {}
public record Zhou(String name) {}
我们有一个 List<City>
,需要找到拥有最多城市的州。
第一步:构建(Histogram)
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. 初始化空列表
List<City> cities = List.of();
- 作用:创建一个不可变的空列表,类型为
City
。 - 特点:
- Java 9+ 支持的工厂方法,语法简洁。
- 列表一旦创建,无法添加或删除元素(不可变)。
2. 流操作与分组统计
Map<Zhou, Long> numberOfCitiesPerState =cities.stream().collect(Collectors.groupingBy(City::state, Collectors.counting()));
- 流程分解:
cities.stream()
:将列表转换为流(Stream),以便进行链式操作。collect(Collectors.groupingBy(...))
:使用收集器对流元素进行分组统计。City::state
:分组依据为City
对象的state
属性(即州)。Collectors.counting()
:统计每个分组的元素数量。
- 结果:
- 返回一个
Map<Zhou, Long>
,键是州对象,值是该州的城市数量。 - 示例:若有 3 个城市属于 “California” 州,则
Map
中会有键值对California → 3L
。
- 返回一个
第二步:找到最多城市的州
Map.Entry<State, Long> stateWithTheMostCities =numberOfCitiesPerState.entrySet().stream().max(Map.Entry.comparingByValue()).orElseThrow();
但这样写可读性不高,因为 Map.Entry<State, Long>
只是个键值对,缺乏语义信息。
第三步:使用 Record
提升可读性
我们可以定义一个新的 Record
,表示 “某个州及其城市数量”:
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()
代码,使其更具可读性:
NumberOfCitiesPerState stateWithTheMostCities =numberOfCitiesPerState.entrySet().stream().map(NumberOfCitiesPerState::new).max(NumberOfCitiesPerState.comparingByNumberOfCities()).orElseThrow();
✅ 优势:
- 代码更加清晰,
NumberOfCitiesPerState
让业务逻辑一目了然。 - 避免直接操作
Map.Entry
,提高可读性和可维护性。
✨ 总结
- 记录类(Record) 适用于不可变数据模型。
- 自定义构造函数 可增强安全性,如防御性拷贝。
- 访问器方法 可定制逻辑,增强封装。
- 序列化时,主构造函数始终被调用,保证一致性。
- 使用 Record 提高代码可读性和业务逻辑清晰度。