1. Introduction
1.导言
In this tutorial, we’ll discuss the possibility of using Optional as a record parameter and why it’s a bad practice.
在本教程中,我们将讨论将 Optional 用作 record 参数的可能性,以及为什么这种做法不好。
2. Intended Uses for Optional
2.可选项的预期用途
Before discussing the relationship between Optional and records, let’s quickly recap the intended uses for Optional in Java.
在讨论 Optional 和记录之间的关系之前,让我们快速回顾一下 Java 中 Optional的预期用途。
Typically, before Java 8, we used null to represent the empty state of an object. However, a null as a return value requires null-check validation from the caller code in runtime. If the caller doesn’t validate, it might get a NullPointerException. And getting the exception is sometimes used to identify the absence of value.
通常,在 Java 8 之前,我们使用 null 表示对象的空状态。但是,作为返回值的 null 需要调用者代码在运行时进行 null 检查验证。如果调用者没有进行验证,可能会收到 NullPointerException 异常。获取异常有时可用于识别无值。
The main goal of Optional is to represent a method return value that represents the absence of a value. Instead of having our application crash with NullPointerExceptions to identify the absence of value, we can use an Optional as the return value. Hence, we know at compilation time that the return value holds something or nothing.
Optional的主要目标是表示一个方法的返回值,该返回值表示没有值。我们可以使用 Optional 作为返回值,而不是让应用程序因NullPointerException而崩溃,以确定没有值。因此,我们在编译时就知道返回值是有值还是无值。
Additionally, as it says in the Java docs:
此外,正如 Java 文档中所述:
Optional is intended to provide a limited mechanism for library method return types where there is a clear need to represent “no result”, and where using null for that is overwhelmingly likely to cause errors
可选的目的是为库方法的返回类型提供一个 有限的机制,在这种情况下,显然需要表示 “无结果”,而使用 null 则极有可能导致错误。
Therefore, it’s also essential to note what Optional isn’t intended to do. In that matter, we can highlight that Optional isn’t intended to be used as an instance field of any class.
因此,还必须注意Optional不打算做什么。在这个问题上,我们可以强调 Optional不打算用作任何类的实例字段。
3. Use-Cases for Java Records
3.Java 记录的用例
Let’s also look at a few concepts about records to get a better foundation about using Optional as a record parameter.
我们还将了解一些有关记录的概念,以便为使用 Optional 作为 record 参数打下更好的基础。
A record is simply a data holder. It fits well when we want to transfer data from one place to another, like from a database to our application.
记录只是一个数据持有者。当我们想把数据从一个地方传输到另一个地方时,比如从数据库传输到我们的应用程序,它就非常适合。
Let’s paraphrase JEP-395:
让我们转述一下 JEP-395 的内容:
Records are classes that act as transparent carriers for immutable data.
记录是作为不可变数据的透明载体的类。
A critical definition of records is that they’re immutable. Hence, once we instantiate one record, all its data remains unmodifiable throughout the rest of the program. That’s great for objects that transfer data, as immutable objects are less error-prone.
记录的一个重要定义就是它们是不可变的。因此,一旦我们实例化了一条记录,它的所有数据在程序的其余部分都是不可修改的。对于传输数据的对象来说,这一点非常重要,因为不可变对象不易出错。
Records also define accessor methods with the same field name automatically. So, by defining them, we get getters with the same of the defined field.
记录还会自动定义具有相同字段名称的访问器方法。因此,通过定义它们,我们可以得到与所定义字段相同的获取器。
The JDK record definition also suggests that the data held by a record should be transparent. As a result, if we call an accessor method, we should get valid data. In that case, valid data means the value that truly represents the object state. Let’s paraphrase Project Amber in that matter:
JDK 记录定义还表明,记录所保存的数据应该是透明的。因此,如果我们调用访问器方法,我们应该获得有效数据。在这种情况下,有效数据指的是真正代表对象状态的值。在这个问题上,让我们引用 Project Amber 的例子:
The API for a data class (Record) models the state, the whole state, and nothing but the state.
数据类(Record)的 API 对状态、整个状态以及除状态之外的所有状态进行建模。
Immutability and transparency are essential definitions to defend the argument that records must not have an Optional parameter.
不变性和透明度是维护记录不得有可选参数这一论点的基本定义。
4. Optional as a Record Parameter
4.可选作为记录参数
Now that we have a better understanding of both concepts, we’ll see why we must avoid using Optional as a record parameter.
现在我们对这两个概念有了更好的理解,我们将了解为什么必须避免使用 Optional 作为记录参数。
First, let’s define a record example:
首先,让我们定义一个记录示例:
public record Product(String name, double price, String description) {
}
We’ve defined a data holder for a product with a name, price, and description. We can imagine data holders resulting from a database query or HTTP call.
我们为产品定义了一个数据持有器,其中包含 名称、价格和 描述。我们可以想象数据库查询或 HTTP 调用所产生的数据持有器。
Now, let’s suppose that the product description sometimes isn’t set. In that case, description is nullable. One way of addressing that is by wrapping the description field into an Optional object:
现在,假设有时没有设置产品描述。在这种情况下,description 将为空。解决这个问题的一种方法是将 description 字段封装到 Optional 对象中:
public record Product(String name, double price, Optional<String> description) {
}
Although the code above compiles correctly, we break the data transparency of the Product record.
虽然上述代码编译正确,但 我们破坏了 Product 记录的数据透明度。
Additionally, record immutability makes it harder to handle an Optional instance than a null variable. Let’s see that in practice with a simple test:
此外,记录不变性使得处理 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));
}
We’ve created a Product with some values and used the generated getters to assert that the record instantiated correctly.
我们创建了一个带有一些值的 Product 并使用生成的获取器断言记录已正确实例化。
In our product, we defined the variables name, price, and description. However, since description is an Optional, we don’t get a value immediately after retrieving it. We need to do some logic to open it up to get the value. In other words, we don’t get the correct object state after calling the accessor method. Thus, it breaks the definition of data transparency of a Java record.
在我们的产品中,我们定义了变量 名称、价格 和 描述。但是,由于 description 是一个 可选项,我们在检索它后并不能立即得到一个值。我们需要执行一些逻辑来打开它以获取值。换句话说,在调用访问器方法后,我们无法获得正确的对象状态。因此,它打破了 Java 记录数据透明的定义。
We may think, in that case, what do we do with nulls? Well, we can simply let them exist. A null represents the empty state of an object, which in that case is more meaningful than an instance of an empty Optional. In those scenarios, we can notify the users of the Product class that description is nullable by using the @Nullable annotation or other good practices for handling nulls.
我们可能会想,在这种情况下,我们该如何处理 nulls 呢?嗯,我们可以简单地让它们存在。null 表示对象的空状态,在这种情况下,它比空 Optional 的实例更有意义。在这些应用场景中,我们可以通过使用 @Nullable 注解或其他 处理 nulls 的良好实践,通知 Product 类的用户 description 是可空的。
Since record fields are immutable, the description field can’t be changed. Hence, to retrieve the description value, we have some options. One is opening it up or returning a default, using orElse() and orElseGet(). Another way is mindlessly using get(), which throws NoSuchElementException if there’s no value. The third is to throw an error if there’s nothing inside of it, using orElseThrow().
由于记录字段是不可变的,因此description字段无法更改。因此,要检索 description 值,我们有一些选择。一种是使用 orElse() 和 orElseGet() 打开它或返回默认值。另一种方法是漫不经心地使用 get(),如果没有值,就会抛出 NoSuchElementException 异常。第三种方法是在没有任何内容的情况下抛出错误,使用 orElseThrow()。
In any possible way of handling it, Optional has no meaning, as in any case, we are either returning null or throwing an error. It’s simpler just to let description be a nullable String.
在任何可能的处理方式中,Optional都没有任何意义,因为在任何情况下,我们要么返回null,要么抛出错误。让 description 成为一个可为空的 String 会更简单。
5. Conclusion
5.结论
In this article, we saw the definitions of a Java record and understood the importance of transparency and immutability.
在本文中,我们了解了 Java 记录的定义,并理解了透明度和不变性的重要性。
We also looked at the intended usage of the Optional class. More importantly, we discussed that Optionals are not suitable to be used as record parameters.
我们还研究了 Optional 类的预期用途。更重要的是,我们讨论了Optional不适合用作记录参数。
As always, you can find the source code over on GitHub.