java 使用record处理不可变数据
record
是jdk14开始出现的预览特性,jdk16正式发布。JEP 395: Records
A record is a class that acts as transparent carrier for immutable data.
record
的作用是作为 不可变数据 的载体, 是为了简化数据类写法的。
比如有如下数据类:
1 | public class Point { |
这个类其实就两个属性x
和y
, 但是我们还得写一大堆代码,还得生成getter
,equals
,toString
,hashCode
方法。
record 登场
record
关键字,极大简化这种数据类的写法:
1 | public record Point(int x, int y) {} |
这一行代码, 创建了如下内容:
- 一个不可变的类, 包含两个字段:x、y 都是int类型。
- 一个标准的构造函数,用来初始化这两个字段
- 默认的
toString()
,equals()
,hashCode()
方法,和Object
类的不一样。组件变化时, 这些方法也会更新 - 可以实现
Serializable
接口,record
类会以特殊的方式序列化。
Record 类
定义Point
类时使用record
关键字代替了class
关键字, record
定义的类自动是final
的,都是java.lang.Record
的子类。
所以record
类不能断承其它类了, 但是实现接口不受影响。
定义record的组件
record类的组件定义在类名后面的形参里: (int x, int y)
, 编译器根据形参自成private final
的字段,名称和参数名一样。也会给每个字段生成同名的访问方法:
1 | public int x() { |
当然, 也可以自己定义字段访问方法。
record类的toString
, equals()
, hashCode()
和普通类继承Object
类不一样,但是也可以重写。
不能对record做的操作
- record类不能定义实例字段
- 不能定义字段的初始化器
- 不能添加实例初始化器
可以添加 static 字段和 static 初始化块
record 的构造函数
编译器为record生成的构造函数,作用是把组件设置到字段上。 可以通过重写构造函数的默认行为。
重写构造函数和普通类的有一些差别, 有两方法:简洁构造函数 和 规范构造函数 。
假设有如下recorde类, 需要重写构造函数:
1 | public record Range(int start, int end) {} |
使用构造函数
假设在创建实例的时候, 我们需要验证 end
要大于start
:
1 | public record Range(int start, int end) { |
简洁构造函数不需要定义参数列表,也不需要为字段赋值,可以正常使用参数。
注意: 这种方式不能直接设置字段值。 但是可以设置参数的值达到一样的效果。
1 | public Range { |
只需要改变参数的值, 编译会在最后为字段赋值。
使用规范构造函数
规范构造函数和普通类的构造函数差不多, 需要为字段赋值。
1 | public record Range(int start, int end) { |
定义其它构造函数
Record可以定义任意的构造函数, 这些构造函数里必须调用规范构造函数, 调用语法和普通类的构造函数调用另一构造函数一样的, 使用this()
.
以State
为例, 它有3个组件:
- 州名:name
- 首都:capitalCity
- 城市列表: cities, 可能为空
现在我们要为State
定义一些自定义的构造函数:
比如:因为record的字段都是不可变的, 所以我们需要为cities
保存一个防御性副本, 防止cities
在record外被修改, 这可以通过重写简洁构造函数,重设cities参数值来实现。 有些州没有城市,则不要cities参数。
1 | public record State(String name, String capitalCity, List<String> cities) { |
访问record 状态
编译器会自动为record的每个组件生成和组件同名的访问方法。 当然也可以重写对应的访问方法。
1 | State state = new State("SiChuan", "ChengDu", List.of("ChengDu", "NanChong", "MianYang")); |
序列化record
如果record类实现了Serializable
, 那么它就可以序列化和反序列化。不过也有限制。
- 可用于替换默认序列化过程的系统均不可用于record。创建 writeObject() 和 readObject() 方法没有效果,实现 Externalizable也没用。
- record可以用作代理对象来序列化其他对象。
readResolve()
方法可以返回一个Record。在record中添加 writeReplace() 也可以。 - 反序列化record时, 总是调用的规范构造函数, 所以定义在里面的校验规则都会被强制执行。
record 使用示例
1 | public class Main { |