1. 概述

在这个教程中,我们将学习如何从一个数据列表或序列中获取第一个非空元素。我们还将探讨在返回昂贵方法链中的第一个非空值时的懒加载评估,并了解如何使用Optional类时,需要**返回第一个非空的Optional**。

2. for循环

在Java 8引入函数式编程之前,通常使用for循环从列表中返回第一个非空元素。

假设我们有一个列表,其第一个元素为null

List<String> objects = Arrays.asList(
    null,
    "first non null",
    "second non null"
);

要返回第一个非空元素,我们可以使用传统的for循环遍历并通过简单的if语句检查每个元素的非空性:

String object = null; 
for (int i = 0; i < objects.size(); i++) {
    if (objects.get(i) != null) {
        object = objects.get(i);
        break;
    }
}

3. 流API

随着Java 8中流API的引入,我们现在能够以更易读、简洁和声明式的方式完成许多常见操作。

要找到第一个非空元素,我们可以通过调用stream()方法对列表进行顺序流处理,并搜索第一个非空元素:

Optional<String> object = objects
  .stream()
  .filter(o -> o != null)
  .findFirst();

我们使用中间操作filter()根据一个Predicate(在这种情况下是简单的null检查lambda表达式o -> o != null)过滤流。最后,我们调用终止操作findFirst()返回满足先前操作的第一个元素,或者一个空的Optional

为了提高可读性,我们可以将Objects.nonNull()方法引用替换为lambda表达式:

Optional<String> object = objects
  .stream()
  .filter(Objects::nonNull)
  .findFirst();

4. 懒加载可能返回null的方法

在这篇文章中,我们假设我们想要从一个随时可用的数据序列中获取第一个非空项。但如果获取值的方法计算成本较高,我们可能希望出于性能原因惰性地评估一系列方法,直到获得第一个非空值

让我们考虑以下可能计算成本较高的方法:

String methodA() {
    return null;
}

String methodB() {
    return "first non null";
}

String methodC() {
    return "second non null";
}

在Java 8之前,我们可能使用一系列if语句:

String object = methodA();
if (object == null) {
    object = methodB();
}

if (object == null) {
    object = methodC();
}

借助流API,我们可以利用函数式接口Supplier实现方法的懒加载

Optional<String> object = Stream
  .<Supplier<String>>of(
      this::methodA, 
      this::methodB,
      this::methodC)
  .map(Supplier::get)
  .filter(Objects::nonNull)
  .findFirst();

在我们的流管道中,当作为map()操作一部分对Supplier函数对象调用get()时,昂贵的方法才会被评估。我们通过使用顺序流来确保懒加载。每个流元素都会通过中间操作filter()的条件检查,一旦找到第一个满足条件的非空元素,流就会终止。

5. 外部库

除了自己实现,我们还可以利用流行的外部库来解决这个问题。

5.1. Apache Commons Lang 3

要使用Apache Commons Lang 3,我们需要在pom.xml中添加以下依赖:

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

给定一个可能是null的单个引用,我们可以使用ObjectUtils.getIfNull()获取非空值,或者从一个延迟计算的备选方法中获取值:

ObjectUtils.getIfNull(object, this::methodB);

对于包含对象的列表,我们可以使用ObjectUtils.firstNonNull(),它接受可变参数:

@Test
void givenListOfObjects_whenUsingApacheCommonsLang3_thenReturnFirstNonNull() {
    String object = ObjectUtils.firstNonNull(objects.toArray(new String[0]));

    assertEquals("first non null", object);
}

如果所有参数都是null,则返回null

此外,我们还可以使用ObjectUtils.getFirstNonNull()来懒加载我们的可空方法:

ObjectUtils.getFirstNonNull(this::methodA, this::methodB, this::methodC);

5.2. Google Guava

要使用Google Guava,我们需要在pom.xml中添加以下依赖:

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

给定两个引用,我们可以使用MoreObjects.firstNonNull()

@Test
void givenTwoObjects_whenUsingGoogleGuavaMoreObjects_thenReturnFirstNonNull() {
    String nullObject = null;
    String nonNullObject = "first non null";
    String object = MoreObjects.firstNonNull(nullObject, nonNullObject);

    assertEquals("first non null", object);
}

但如果有两个参数都为null,则会抛出NullPointerException

对于列表,我们可以使用Iterables.find()Predicates.nonNull()获取第一个非空值:

@Test
void givenListOfObjects_whenUsingGoogleGuavaIterables_thenReturnFirstNonNull() {
    String object = Iterables.find(objects, Predicates.notNull());

    assertEquals("first non null", object);
}

6. Optional

如果这篇文章没有提及Optional类型,那将是不完整的。这个类在Java 8中引入,专门解决了null引用的不足。

Optional类型允许开发者明确表示方法或变量可能有值也可能无值。因此,以前返回null或值的方法现在返回Optional

因此,我们之前的问题“返回第一个非空值”变成了一个微妙的不同问题。我们应该如何返回第一个非空的Optional

让我们考虑这样一个列表,其第一个元素为空:

List<Optional<String>> optionals = Arrays.asList(
    Optional.<String> empty(), 
    Optional.of("first non empty"), 
    Optional.of("second non empty")
);

我们可以对列表进行流处理并搜索第一个非空元素:

@Test
void givenListOfOptionals_whenStreaming_thenReturnFirstNonEmpty() {
    Optional<String> object = optionals.stream()
      .filter(Optional::isPresent)
      .map(Optional::get)
      .findFirst();

    assertThat(object).contains("first non empty");
}

我们使用ifPresent()方法检查每个给定的Optional是否有值。如果元素满足这个断言,那么我们可以在map()中间操作的一部分安全地获取值,使用get()

7. 总结

在这篇文章中,我们探讨了如何使用自己的实现以及外部库来获取第一个非空值。我们也考虑了在返回昂贵方法链中的第一个非空值时的懒加载评估。最后,我们展示了如何返回第一个非空的Optional,因为自从Java 8引入以来,这可能是更合适的用例。

本文中的代码示例可在GitHub上找到。