Records:不可变数据的简洁声明

Java 14引入的Records是一种新型的类声明形式,旨在以简洁的方式扮演不可变数据的透明载体。传统的Java类在定义纯数据传输对象(DTO)时,往往需要编写大量重复的样板代码,如构造函数、访问器方法(getters)、equals()、hashCode()和toString()等。Records通过语义化的声明,由编译器自动生成这些成员,极大地简化了代码。

一个典型的Record声明如下所示:

public record User(String name, String email, int age) {}

这行简短的代码等价于一个final类,其中包含了所有字段的private final声明、一个规范的构造函数、以及所有字段的访问器(如此处的`name()`、`email()`和`age()`),并自动实现了equals()、hashCode()和toString()方法。Records强制要求其状态在创建后不可变,这从根本上保证了数据的一致性,减少了因对象可变性而引发的潜在错误,从而重构和强化了数据模型的健壮性。

Sealed Classes:受控的继承层次结构

Java 17正式引入的Sealed Classes(密封类)为语言带来了前所未有的对继承关系的精确控制能力。在传统Java设计中,一个类只要不是final的,就可以被任何类无限地扩展,这可能导致脆弱的类层次结构和难以维护的代码。Sealed Classes通过`sealed`和`permits`关键字,明确指定哪些类或接口可以继承或实现它,从而将继承关系限制在一个有限的、已知的范围内。

一个Sealed Class的声明示例如下:

public sealed interface Shape permits Circle, Rectangle, Triangle {    double area();}public final class Circle implements Shape { ... }public final class Rectangle implements Shape { ... }public non-sealed class Triangle implements Shape { ... }

在此例中,`Shape`接口被声明为`sealed`,并只允许(`permits`)`Circle`、`Rectangle`和`Triangle`这三个类实现它。这些子类必须被显式声明为`final`(禁止进一步扩展)、`sealed`(允许但受控扩展)或`non-sealed`(重新开放扩展)。这项特性通过编译时的严格检查,确保了类层次结构的完整性和可预测性,使得在`switch`表达式等场景中进行穷尽性检查成为可能,从而重构了面向对象设计的边界与契约。

Records与Sealed Classes的协同重塑

Records与Sealed Classes的结合使用,为构建高度表达力且安全的数据模型提供了强大的组合工具。Records负责提供简洁、安全的数据载体,而Sealed Classes负责定义清晰、受限的类型层次,二者协同可以从根本上重塑代码的设计模式。

一个典型的应用场景是构建代数数据类型(Algebraic Data Type, ADT)。例如,在构建一个表达式树的模型中:

public sealed interface Expr permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {}public record ConstantExpr(int value) implements Expr {}public record PlusExpr(Expr left, Expr right) implements Expr {}public record TimesExpr(Expr left, Expr right) implements Expr {}public record NegExpr(Expr expr) implements Expr {}

在这个设计中,`Expr`是一个密封接口,限定了所有可能的表达式类型。每个实现都是一个Record,清晰地表征了该类型所承载的数据。这种设计模式带来了多重优势:

首先,它确保了类型的绝对安全。编译器知道所有可能的`Expr`子类型,因此在使用模式匹配的`switch`表达式时,可以进行穷尽性检查,避免运行时因遗漏分支而导致的错误。

其次,代码极其简洁且意图明确。数据结构和它们之间的关系一目了然,消除了大量冗余代码。最后,这种不可变性和受控的层次结构使得程序更易于推理、测试和维护,实现了对复杂业务逻辑的优雅建模与重构。

重构现有代码的设计模式

将Records和Sealed Classes应用于现有项目的重构,可以显著提升代码质量。

1. 替代繁琐的DTO/VO: 项目中大量的POJO类,如数据传输对象(DTO)或值对象(VO),通常只包含状态和访问方法,是使用Record的理想候选。将其重构成Record可以立即减少大量样板代码,并强化其不可变性。

2. 明确状态和错误类型: 在处理如操作结果、状态码或错误信息时,常常会定义一系列的类或枚举。使用Sealed Hierarchy(密封层次结构)可以更类型安全地表达这些概念。例如,一个API的返回结果可以设计为`sealed interface ApiResult permits Success, Failure`,其中`Success`是一个Record包装了数据,`Failure`是另一个Record包装了错误信息。这迫使调用方必须显式处理成功和失败两种情形。

3. 取代工厂方法和策略模式的部分实现: 传统的工厂方法常常返回一个广泛的接口类型,具体类型对客户端是不透明的。结合使用Sealed Classes和Records,可以定义一个返回类型为密封接口的工厂方法,而所有具体的实现类型都是有限的、已知的。这使得客户端代码可以使用模式匹配来安全地处理不同的情况,而不必依赖向下转型或复杂的if-else链条。

综上所述,Java的Records和Sealed Classes不仅是语法糖,更是强大的设计工具。它们通过赋予开发者更强的表现力和约束力,能够从架构层面重塑代码,使其更简洁、更安全、更健壮,代表了现代Java向更具表现力和安全性的语言演进的重要方向。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐