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) {
}

我们定义了一个包含 namepricedescription 的产品数据载体,可能来自数据库查询或 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 验证实例化是否正确。

在产品中,我们定义了 namepricedescription 变量。但由于 descriptionOptional,获取后不能直接得到值,需要额外逻辑才能解包。换句话说,调用访问器方法后无法直接获取真实对象状态,这违背了 Java Record 的数据透明性定义。

那么如何处理 null 呢?其实直接允许 null 存在即可。null 能清晰表示对象的空状态,这比空的 Optional 实例更有意义。这种场景下,我们可以通过 @Nullable 注解或其他处理 null 的最佳实践告知 Product 类的使用者 description 可为空。

由于 Record 字段不可变,description 字段无法修改。要获取其值,我们有几种选择:

  1. 使用 orElse()orElseGet() 解包或返回默认值
  2. 直接调用 get()(无值时抛出 NoSuchElementException
  3. 使用 orElseThrow() 在无值时抛出异常

无论哪种处理方式,Optional 都失去意义——最终要么返回 null,要么抛出异常。不如直接让 description 作为可空的 String 更简单。

5. 结论

本文探讨了 Java Record 的核心定义,强调了数据透明性和不可变性的重要性。

我们也分析了 Optional 类的设计用途。更重要的是,我们论证了 Optional 不适合作为 Record 参数

完整源代码可在 GitHub 获取。