Java中使用Optional检测并获得非空值的几种方法

Optional是 Java 8 中作为java.util包的一部分引入的类。它充当可能包含也可能不包含非空值的容器。 Optional可以帮助我们更有效地处理null值并避免代码中出现NullPointerException 。

什么是Optional类?
在 Java 中,引用可能指向也可能不指向内存中的对象。换句话说,引用可以为null。因此,这可能会引发NullPointerException异常。
为了解决这个问题,Java 8 中引入了Optional类。

将引用包装在Optional中可以让我们更好地表达值存在或不存在的可能性。

此外,我们可以利用Optional类上的各种实用方法(例如isPresent()) 来避免遇到NullPointerException。


本文介绍:

  1. 使用Optional时的一项常见任务是检查它是否包含等于特定对象的值?
  2. 使用静态工厂方法Optional.of()和Optional.ofNullable()方法来获取给定引用的Optional 


1、如何检查Optional是否包含等于T对象的值

首先,我们先明确一下等于检查的要求。

假设我们有两个对象;一个是类型T的非 null valueOfT对象,另一个是类型Optional<T>的opt实例。

  • 如果opt为空,则它不包含任何 value。也就是说,它里面的值不能等于任何目标对象,因此检查将始终返回false。
  • 相反,如果opt是“present”,我们需要验证opt携带的值是否等于valueOfT。

因此,简单地说,我们将执行“ opt present and equals valueOfT ”检查。

在本教程中,我们将探索完成该任务的各种方法。为了简单起见,我们将使用String作为T的示例来演示每种方法。让我们创建两个字符串常量:

static final String A_B_C = "a b c";
static final String X_Y_Z =
"x y z";

我们将在单元测试中使用这些字符串值来展示不同方法的结果。

接下来,让我们深入研究代码并研究如何实现检查。

equals()方法
Optional类重写了equals()方法。如果存在两个可选对象并且它们保存的值相等,则该方法返回true。因此,我们可以首先从valueOfT创建一个Optional实例,然后使用Optional.equals() 与给定的opt对象执行检查:

opt.isPresent() && opt.equals(Optional.of(valueOfT));

接下来,我们来检查一下这种方法是否能按预期工作。

首先,我们看一下opt不存在的情况:

Optional<String> opt = Optional.empty();
assertFalse(opt.isPresent() && opt.equals(Optional.of(A_B_C)));

正如我们提到的,如果opt为空,则无论valueOfT 的值是什么,整个检查都应该返回false。

接下来,让我们看看当opt存在时,这种方法是否会产生预期的结果:

opt = Optional.of(X_Y_Z);
assertFalse(opt.isPresent() && opt.equals(Optional.of(A_B_C)));
 
opt = Optional.of(A_B_C);
assertTrue(opt.isPresent() && opt.equals(Optional.of(A_B_C)));

正如我们所看到的,这种方法解决了问题,但它创建了一个中间的Optional对象。

Optional.get()
检查opt中包含的值是否等于valueOfT 的直接方法是首先使用Optional.get()检索opt中的值,然后验证其与valueOfT是否相等:

opt.isPresent() && opt.get().equals(valueOfT);

接下来,让我们遵循此模式并使用相同的输入来验证它是否产生正确的结果:

Optional<String> opt = Optional.empty();
assertFalse(opt.isPresent() && opt.get().equals(A_B_C));
 
opt = Optional.of(X_Y_Z);
assertFalse(opt.isPresent() && opt.get().equals(A_B_C));
 
opt = Optional.of(A_B_C);
assertTrue(opt.isPresent() && opt.get().equals(A_B_C));

正如代码所示,该解决方案不会创建额外的对象。

使用Optional.map()和Optional.orElse()
我们期望在“ opt present and equals valueOfT ”检查后获得一个布尔值。因此,我们可以将这个问题视为通过遵循一定的规则将Optional对象转换为布尔值。

Optional类提供map() 来将当前的Optional转换为另一个携带不同值的Optional。

此外,orElse()方法返回由Optional实例包装的值(如果存在)。否则,如果Optional为空,orElse()返回我们指定的值。

因此,我们可以结合这两种方法来执行相等性检查并从给定的Optional中获取一个布尔值:

opt.map(v -> v.equals(valueOfT)).orElse(false);

接下来,让我们看看它是否适用于我们的输入:

Optional<String> opt = Optional.empty();
assertFalse(opt.map(A_B_C::equals).orElse(false));
 
opt = Optional.of(X_Y_Z);
assertFalse(opt.map(A_B_C::equals).orElse(false));
 
opt = Optional.of(A_B_C);
assertTrue(opt.map(A_B_C::equals).orElse(false));

此解决方案还会产生一个由可选对象map() 创建的中间对象。然而,这是一种实用且流畅的方法。

2、使用静态工厂方法Optional.of()和Optional.ofNullable()方法来获取给定引用的Optional 
了解了静态工厂方法Optional.of()和Optional.ofNullable()之间的主要区别以及何时最适合使用它们。

Optional.of ()方法
当我们确定有一个非空引用时,我们应该使用Optional.of()静态工厂方法。

假设我们有一个本地String 变量,我们想从中获取一个Optional:

@Test
void givenNonNullReference_whenUsingOptionalOf_thenObtainOptional() {
    String s = "no null here";
    assertThat(Optional.of(s))
      .isNotEmpty()
      .hasValue(
"no null here");
}

从我们的断言中,我们可以看到我们的可选值不为空。换句话说,实用程序方法isPresent()(返回Optional是否有值)将返回true。 

但是,当我们对空引用使用此方法时,我们将遇到NullPointerException。

@Test
void givenNullReference_whenUsingOptionalOf_thenNullPointerExceptionThrown() {
    String s = null;
    assertThatThrownBy(() -> Optional.of(s))
      .isInstanceOf(NullPointerException.class);
}

Optional.ofNullable ()方法
当我们有一个可能为空或不为空的引用时,我们应该使用Optional.ofNullable()静态工厂方法。因此,对于null的引用,我们不会遇到NullPointerException。相反,我们将获得一个空的Optional:

@Test
void givenNullReference_whenUsingOptionalOfNullable_thenObtainOptional() {
    String s = null;
    assertThat(Optional.ofNullable(s)).isEmpty();
}

公平地说,为什么我们不应该总是使用Optional.ofNullable()而不是Optional.of()。

使用Optional.of()允许我们通过抛出异常来立即停止代码的执行。正如我们之前在获取null引用的Optional时发现的那样,就会发生这种情况。换句话说,使用Optional.of()方法可以让我们遵守早期失败的原则。

另外,我们可能会遇到开发人员使用这些静态工厂方法作为使用函数式编程的入口点。这是通过使用Optional类上的方法来实现的,这些方法使用函数对象作为方法参数,例如map()。