1. 概述

在这个简短的教程中,我们将解释如何在使用流API时避免NoSuchElementException

首先,我们会解释异常的主要原因,然后通过实际例子展示如何重现和修复它。

2. 异常的原因

深入理解之前,先了解一下这个异常的含义。

简单来说,NoSuchElementException会在请求的元素不存在时抛出。例如,尝试访问一个不存在或不存在的元素会导致此异常。

在使用流API时,通常在空的Optional实例上调用get()方法是导致NoSuchElementException的常见原因之一。

3. 引发异常的过程

现在我们了解了异常,接下来实践一下看看如何在实际操作中重现它。

例如,我们可以创建一个名字列表并使用流API进行过滤:

@Test(expected = NoSuchElementException.class)
public void givenEmptyOptional_whenCallingGetMethod_thenThrowNoSuchElementException() {
    List<String> names = List.of("William", "Amelia", "Albert", "Philip");
    Optional<String> emptyOptional = names.stream()
      .filter(name -> name.equals("Emma"))
      .findFirst();

    emptyOptional.get();
}

如图所示,我们使用了filter()方法来查找名字"Emma",并且接着使用findFirst()方法获取第一个找到的元素,或者如果过滤后的流为空,则返回一个空的Optional

在这里,我们的列表中不包含名字"Emma",所以findFirst()返回一个空的Optional由于我们试图获取一个不存在的名字,且空的Optional没有值,因此测试用例会因NoSuchElementException异常失败

4. 避免异常

现在让我们看看如何修复这个异常。最简单的方法是在调用get()方法之前检查Optional实例中是否有值。

幸运的是,流API提供了特别为此目的的isPresent()方法。让我们来看看它的用法:

@Test
public void givenEmptyOptional_whenUsingIsPresentMethod_thenReturnDefault() {
    List<String> names = List.of("Tyler", "Amelia", "James", "Emma");
    Optional<String> emptyOptional = names.stream()
      .filter(name -> name.equals("Lucas"))
      .findFirst();

    String name = "unknown";
    if (emptyOptional.isPresent()) {
        name = emptyOptional.get();
    }

    assertEquals("unknown", name);
}

这里,我们在调用get()方法前使用isPresent()确保Optional实例中有值。这样可以避免NoSuchElementException异常。

请注意,使用isPresent()会带来if-else语句的开销。那么,是否可以做得更好呢?当然可以!

通常来说,最好的做法是使用orElse()方法。简而言之,这个方法如果值存在就返回该值,否则返回给定的备选参数

@Test
public void givenEmptyOptional_whenUsingOrElseMethod_thenReturnDefault() {
    List<String> names = List.of("Nicholas", "Justin", "James");
    Optional<String> emptyOptional = names.stream()
      .filter(name -> name.equals("Lucas"))
      .findFirst();

    String name = emptyOptional.orElse("unknown");

    assertEquals("unknown", name);
}

如上所述,这种方法提供了一种更方便、直接的方式来避免NoSuchElementException

另外,我们也可以使用orElseGet()方法达到同样的效果:

@Test
public void givenEmptyOptional_whenUsingOrElseGetMethod_thenReturnDefault() {
    List<String> names = List.of("Thomas", "Catherine", "David", "Olivia");
    Optional<String> emptyOptional = names.stream()
      .filter(name -> name.equals("Liam"))
      .findFirst();

    String name = emptyOptional.orElseGet(() -> "unknown");

    assertEquals("unknown", name);
}

orElse()不同,orElseGet()接受一个供应者作为参数。另一个关键区别是,orElse()在所有情况下都会执行,即使Optional实例已经有值。然而,orElseGet()只有在Optional值不存在时才会执行

关于orElse()orElseGet()方法的区别,请参阅我们的文章详细讨论

5. 避免NoSuchElementException的最佳实践

总之,在使用流API时,要避免NoSuchElementException异常,需要注意以下几点:

  • 在调用get()方法之前始终检查返回的流或Optional是否不为空。
  • 尝试使用orElse()orElseGet()定义备选值。
  • 在调用流的终端操作之前,先进行过滤。

6. 总结

在这篇短文中,我们探讨了在使用流API时避免NoSuchElementException异常的不同方法。

在过程中,我们展示了如何重现异常以及如何通过实际例子避免它。

如往常一样,所有示例的完整源代码可在GitHub上找到。