Convert Null Value to a Default Value in Java – 在 Java 中将空值转换为默认值

最后修改: 2024年 1月 11日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In 1965, Tony Hoare introduced the concept of a null reference. Since then, countless hours have been spent reading the logs and trying to find the source of NullPointerExceptions. This exception is so ubiquitous that it’s common to refer to it as NPE.

1965 年,Tony Hoare 引入了引用的概念。从那时起,人们花费了无数的时间阅读日志,试图找到NullPointerExceptions的来源。这种异常非常普遍,以至于人们通常将其称为 NPE

In this tutorial, we’ll learn how to mitigate this problem. We’ll review several techniques that simplify converting nulls to default values.

在本教程中,我们将学习如何缓解这一问题。我们将回顾几种简化将 null 转换为默认值的技术。

2. Simple if Statements

2.简单的 if 语句

The easiest way to approach the conversion is to use if statements. They’re basic language structures and benefit from being clear to developers with different experiences and levels. The best part of this approach is that it’s verbose, which simultaneously is the worst part:

最简单的转换方法是使用 if 语句它们是基本的语言结构,对于具有不同经验和水平的开发人员来说,清晰明了的语句会让他们受益匪浅。这种方法的最大优点是冗长,同时也是最糟糕的地方:

@ParameterizedTest
@ArgumentsSource(ObjectsProvider.class)
void givenIfWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) {
    String actual = defaultValue;
    if (givenValue != null) {
        actual = givenValue;
    }
    assertDefaultConversion(givenValue, defaultValue, actual);
}

Because we have total control over the logic, we can easily change, extract, and reuse it. Additionally, we can make it lazy if we want:

由于我们可以完全控制逻辑,因此可以轻松地更改、提取和重用它。此外,如果我们愿意,还可以让它 变得更简单

@ParameterizedTest
@ArgumentsSource(ObjectsSupplierProvider.class)
void givenIfWhenNotNullThenReturnsDefault(String givenValue, String expected, Supplier<String> expensiveSupplier) {
    String actual;
    if (givenValue != null) {
        actual = givenValue;
    } else {
        actual = expensiveSupplier.get();
    }
    assertDefaultConversion(givenValue, expected, actual);
}

If the operations are quite simple, we can use a ternary operator to make them more inlined. The Elvis operator didn’t make it into Java, but we still can improve the code:

如果操作非常简单,我们可以使用辅助操作符使其更加内联。Elvis 运算符 虽然没有应用到 Java 中,但我们仍然可以改进代码:

@ParameterizedTest
@ArgumentsSource(ObjectsProvider.class)
void givenTernaryWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) {
    String actual = givenValue != null ? givenValue : defaultValue;
    assertDefaultConversion(givenValue, defaultValue, actual);
}

Also, it allows a lazy approach as only the required expressions are evaluated:

此外,它还允许使用懒惰方法,因为只对所需表达式进行评估:

@ParameterizedTest
@ArgumentsSource(ObjectsSupplierProvider.class)
void givenLazyTernaryWhenNotNullThenReturnsDefault(String givenValue, String expected, 
  Supplier<String> expensiveSupplier) {
    String actual = givenValue != null ? givenValue : expensiveSupplier.get();
    assertDefaultConversion(givenValue, expected, actual);
}

We can extract this logic into a separate method with a good name to make our code more readable. However, Java and some external libraries have done it already.

我们可以将这些逻辑提取到一个单独的方法中,并为其取一个好名字,以使我们的代码更具可读性。不过,Java 和一些外部库已经做到了这一点。

3. Java Objects

3. Java 对象</em

Java 9 provides us with two utility methods: Objects.requireNonNullElse and Objects.requireNonNullElseGet. These methods have implementations similar to those we reviewed. Overall, they provide a better API and make the code self-explanatory:

Java 9 为我们提供了两种实用方法:Objects.requireNonNullElseObjects.requireNonNullElseGet。这些方法的实现与我们审查过的方法类似。总的来说,这些方法提供了更好的 API,并使代码不言自明:

@ParameterizedTest
@ArgumentsSource(ObjectsProvider.class)
void givenObjectsWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) {
    String actual = requireNonNullElse(givenValue, defaultValue);
    assertDefaultConversion(givenValue, defaultValue, actual);
}

Static imports can help us remove the Objects class name to reduce the noise. The lazy version looks like this:

静态导入可以帮助我们删除 Objects 类名,从而减少噪音。懒惰版本如下

@ParameterizedTest
@ArgumentsSource(ObjectsSupplierProvider.class)
void givenLazyObjectsWhenNotNullThenReturnsDefault(String givenValue, String expected,
  Supplier<String> expensiveSupplier) {
    String actual = requireNonNullElseGet(givenValue, expensiveSupplier);
    assertDefaultConversion(givenValue, expected, actual);
}

However, this API is accessible starting only from Java 9. At the same time, Java 8 also provides some convenient tools to achieve a similar result.

不过,只有从 Java 9 开始才能访问此 API。同时,Java 8 也提供了一些方便的工具来实现类似的结果。

4. Java Optional<T>

4. java 可选项<T>

The main idea behind the Optional<T> class was to fight the issue with null checks and NullPointerExceptions. It’s possible to identify nullable APIs in documentation, but a better solution is to show it explicitly in the code. Getting an Optional<T> from some method unambiguously tells us the value might be null. Also, IDEs can use static analysis for notifications and highlighting.

Optional<T> 类背后的主要想法是解决 null 检查和 NullPointerExceptions 的问题。可以在文档中识别 nullable API,但更好的解决方案是在代码中明确显示。从某些方法中获取Optional<T>可以明确地告诉我们该值可能是null此外,集成开发环境可以使用静态分析来进行通知和高亮显示。

Explicit null checks weren’t the goal of this class. However, we can use it to wrap a value we would like to check and do some operations over it:

明确的 null 检查并不是该类的目标。不过,我们可以用它来封装我们想要检查的值,并对其进行一些操作:

@ParameterizedTest
@ArgumentsSource(ObjectsProvider.class)
void givenOptionalWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) {
    String actual = Optional.ofNullable(givenValue).orElse(defaultValue);
    assertDefaultConversion(givenValue, defaultValue, actual);
}

The lazy version looks quite similar:

懒人版看起来很相似:

@ParameterizedTest
@ArgumentsSource(ObjectsSupplierProvider.class)
void givenLazyOptionalWhenNotNullThenReturnsDefault(String givenValue, String expected,
  Supplier<String> expensiveSupplier) {
    String actual = Optional.ofNullable(givenValue).orElseGet(expensiveSupplier);
    assertDefaultConversion(givenValue, expected, actual);
}

Creating a separate wrapper object for a null check might be questionable. At the same time, it might be useful for reaching through data objects without chained null checks:

null检查创建一个单独的包装器对象可能值得商榷。同时,它可能有助于在没有链式null检查的情况下通过数据对象进行检查:

@Override
public Delivery calculateDeliveryForPerson(Long id) {
    Person person = getPersonById(id);
    if (person != null && person.getAddress() != null && person.getAddress().getZipCode() != null) {
        ZipCode zipCode = person.getAddress().getZipCode();
        String code = zipCode.getCode();
        return calculateDeliveryForZipCode(code);
    }
    return Delivery.defaultDelivery();
}

We can do the same, but using Optional.map(Function<T, U>):

我们也可以这样做,但要使用 Optional.map(Function<T, U>)

public Delivery calculateDeliveryForPerson(Long id) {
    return Optional.ofNullable(getPersonById(id))
      .map(Person::getAddress)
      .map(Address::getZipCode)
      .map(ZipCode::getCode)
      .map(this::calculateDeliveryForZipCode)
      .orElse(Delivery.defaultDelivery());
}

Wrapping objects in Optional<T> early on can reduce the checking we must do later.

尽早用 Optional<T> 封装对象可以减少我们之后必须进行的检查。

5. Guava Library

5 番石榴图书馆

We can import Guava to get a similar functionality if all the previous methods aren’t suitable, for example, when using earlier versions of Java. Let’s start by adding the dependency:

如果前面的方法都不适用,例如使用早期版本的 Java,我们可以导入 Guava 以获得类似的功能。首先,让我们添加 依赖关系

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>33.0.0-jre</version>
</dependency>

It mirrors the Java functionality and doesn’t add any explicitly useful features. To get a default value if the provided is null, we can use MoreObjects:

它反映了 Java 的功能,并没有添加任何明确有用的功能。要在提供的值为 null 时获取默认值,我们可以使用 MoreObjects:

@ParameterizedTest
@ArgumentsSource(ObjectsProvider.class)
void givenGuavaWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) {
    String actual = MoreObjects.firstNonNull(givenValue, defaultValue);
    assertDefaultConversion(givenValue, defaultValue, actual);
}

MoreObjects replaces the Guava’s Objects utility class, which was deprecated and planned for removal. However, it doesn’t allow the default value to be supplied lazily. Also, it provides an Optional<T> class with the same name as Java but resides in a different package:

MoreObjects替换了 Guava 的 Objects 实用程序类,该类已废弃并计划删除。但是,它不允许随意提供默认值。此外,它还提供了一个Optional<T>类,其名称与 Java 相同,但位于不同的包中:

@ParameterizedTest
@ArgumentsSource(ObjectsProvider.class)
void givenGuavaOptionalWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) {
    String actual = com.google.common.base.Optional.fromNullable(givenValue).or(defaultValue);
    assertDefaultConversion(givenValue, defaultValue, actual);
}

We can also implement a chain of modifications using this class as well:

我们还可以使用该类实现一系列修改:

@Override
public Delivery calculateDeliveryForPerson(Long id) {
    return Optional.fromNullable(getPersonById(id))
      .transform(Person::getAddress)
      .transform(Address::getZipCode)
      .transform(ZipCode::getCode)
      .transform(this::calculateDeliveryForZipCode)
      .or(Delivery.defaultDelivery());
}

The transform method doesn’t allow null-returning functions. Thus, we must ensure that none of the methods in the pipeline returns null. Overall, Guava is a good substitution for Java features if they’re unavailable, but it provides less functionality than Java Optional<T>.

transform方法不允许 null 返回函数。因此,我们必须确保管道中没有任何方法返回 null。总的来说,如果 Java 功能不可用,Guava 是一个很好的替代品,但它提供的功能少于 Java Optional<T>

6. Apache Commons

6.阿帕奇共享资源

Another library we can use to simplify our null checks is Apache Commons. Let’s add dependency:

Apache Commons 是我们可以用来简化 null 检查的另一个库。让我们添加 依赖关系

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
</dependency>

However, it provides only simple methods to get the first non-null value out of several arguments:

不过,它只提供了从多个参数中获取第一个非值的简单方法:

@ParameterizedTest
@ArgumentsSource(ObjectsProvider.class)
void givenApacheWhenNotNullThenReturnsDefault(String givenValue, String defaultValue) {
    String actual = ObjectUtils.firstNonNull(givenValue, defaultValue);
    assertDefaultConversion(givenValue, defaultValue, actual);
}

The lazy version is a little bit inconvenient API, as it requires Supplier<T>, so we’ll have to wrap a value if we already have one:

懒惰版本的 API 有点不方便,因为它需要 Supplier<T> ,所以如果我们已经有一个值,就必须再封装一个值:

@ParameterizedTest
@ArgumentsSource(ObjectsSupplierProvider.class)
void givenLazyApacheWhenNotNullThenReturnsDefault(String givenValue, String expected,
  Supplier<String> expensiveSupplier) {
    String actual = ObjectUtils.getFirstNonNull(() -> givenValue, expensiveSupplier);
    assertDefaultConversion(givenValue, expected, actual);
}

Overall, this is also a nice substitution for Java features if they’re not accessible to us for any reason.

总之,如果我们因故无法使用 Java 功能,这也是一个很好的替代品。

7. Conclusion

7.结论

NullPointerException is the most common exception developers face. There are several convenient ways to ensure null safety. Java APIs and external libraries provide many techniques. However, there’s nothing shameful to fall back to simple if statements, as it’s clean, simple, and explicit.

NullPointerException 是开发人员最常遇到的异常。有几种方便的方法可以确保 Null 安全。Java API 和外部库提供了许多技术。不过,回到简单的 if 语句并不可耻,因为它干净、简单、明确。

The main goal of null checking is to do it as early as possible and ensure it’s uniform across the project. The way we’re doing it isn’t essential.

null 检查的主要目的是尽早进行,并确保整个项目的一致性。我们的方法并不是必不可少的。

As usual, all the code from the tutorial is available over on GitHub.

与往常一样,教程中的所有代码都可以在 GitHub 上获取