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上找到。