1. 引言
本文将探讨在 Java Record 中使用 Optional
作为参数的可能性,以及为什么这是一种糟糕的实践。
2. Optional 的设计初衷
在讨论 Optional
与 Record 的关系前,我们先快速回顾 Optional
在 Java 中的设计用途。
在 Java 8 之前,我们通常用 null
表示对象的空状态。但 null
作为返回值时,调用方代码必须在运行时进行空值检查。如果调用方忘记验证,就可能触发 NullPointerException
,有时甚至会用异常来标识"无值"状态。
Optional
的核心目标是表示方法返回值可能不存在的情况。与其让应用因 NullPointerException
崩溃来标识无值状态,不如使用 Optional
作为返回值。这样我们能在编译时就明确知道返回值可能包含内容也可能为空。
此外,Java 官方文档 明确指出:
Optional 旨在为库方法返回类型提供有限机制,当需要明确表示"无结果"且使用
null
极易引发错误时使用。
因此必须注意 Optional
不该做什么:**Optional
不应作为任何类的实例字段使用**。
3. Java Record 的适用场景
让我们先了解 Record 的几个关键概念,为后续讨论 Optional
作为 Record 参数做铺垫。
Record 本质是数据载体。当我们需要在不同组件间传输数据时(如从数据库到应用),Record 非常适用。
根据 JEP-395 的描述:
Record 是作为不可变数据的透明载体的类。
Record 的关键特性是不可变性:一旦实例化,其所有数据在程序生命周期内都不可修改。这对数据传输对象特别有利,因为不可变对象更不易出错。
Record 还会自动生成与字段同名的访问器方法。定义字段后,我们直接获得同名的 getter。
JDK 对 Record 的定义还强调其数据应具有透明性:调用访问器方法时,应能直接获取有效数据。正如 Project Amber 所述:
数据类(Record)的 API 应建模状态、完整的状态、且仅建模状态。
不可变性和透明性这两个核心特性,正是反对在 Record 中使用 Optional
参数的关键论据。
4. 在 Record 中使用 Optional 参数
现在我们已充分理解两个概念,接下来分析为什么必须避免在 Record 中使用 Optional
参数。
先看一个 Record 示例:
public record Product(String name, double price, String description) {
}
我们定义了一个包含 name
、price
和 description
的产品数据载体,可能来自数据库查询或 HTTP 调用。
假设产品描述有时可能未设置,即 description
可为空。一种处理方式是将 description
字段包装为 Optional
:
public record Product(String name, double price, Optional<String> description) {
}
虽然上述代码能正常编译,但这破坏了 Product
Record 的数据透明性。
此外,Record 的不可变性使得处理 Optional
实例比处理 null
变量更麻烦。通过一个简单测试来验证:
@Test
public void givenRecordCreationWithOptional_thenCreateItProperly() {
var emptyDescriptionProduct = new Product("television", 1699.99, Optional.empty());
Assertions.assertEquals("television", emptyDescriptionProduct.name());
Assertions.assertEquals(1699.99, emptyDescriptionProduct.price());
Assertions.assertNull(emptyDescriptionProduct.description().orElse(null));
}
我们创建了一个带值的 Product
,并用生成的 getter 验证实例化是否正确。
在产品中,我们定义了 name
、price
和 description
变量。但由于 description
是 Optional
,获取后不能直接得到值,需要额外逻辑才能解包。换句话说,调用访问器方法后无法直接获取真实对象状态,这违背了 Java Record 的数据透明性定义。
那么如何处理 null
呢?其实直接允许 null
存在即可。null
能清晰表示对象的空状态,这比空的 Optional
实例更有意义。这种场景下,我们可以通过 @Nullable
注解或其他处理 null
的最佳实践告知 Product
类的使用者 description
可为空。
由于 Record 字段不可变,description
字段无法修改。要获取其值,我们有几种选择:
- 使用
orElse()
或orElseGet()
解包或返回默认值 - 直接调用
get()
(无值时抛出NoSuchElementException
) - 使用
orElseThrow()
在无值时抛出异常
无论哪种处理方式,Optional
都失去意义——最终要么返回 null
,要么抛出异常。不如直接让 description
作为可空的 String
更简单。
5. 结论
本文探讨了 Java Record 的核心定义,强调了数据透明性和不可变性的重要性。
我们也分析了 Optional
类的设计用途。更重要的是,我们论证了 Optional
不适合作为 Record 参数。
完整源代码可在 GitHub 获取。