在 Java 中傳回第一個非空值
1. 概述
在本教程中,我們將學習如何從清單或資料序列中傳回第一個非null
元素。
當從昂貴的方法鏈中傳回第一個非null
時,我們也會探討惰性求值。最後,我們將發現使用Optional
類別將如何要求我們傳回第一個非空Optional
。
for
循環
在 Java 8 中引入函數式程式設計之前,通常使用for
迴圈傳回列表中的第一個非null
元素。
讓我們考慮一個第一個元素為null
的列表:
List<String> objects = Arrays.asList(
null,
"first non null",
"second non null"
);
要傳回第一個非null
元素,我們可以使用傳統的for
迴圈來迭代並透過簡單的if
語句檢查每個元素是否為 null:
String object = null;
for (int i = 0; i < objects.size(); i++) {
if (objects.get(i) != null) {
object = objects.get(i);
break;
}
}
3. 串流媒體介面
隨著 Java 8 中Stream API的引入,我們現在能夠以更易讀、更簡單和宣告性的方式完成許多常見模式。
為了找到第一個非null
元素,我們透過呼叫stream()
方法按順序串流列表並蒐索第一個非null
元素:
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
的方法的惰性求值
在整篇文章中,我們假設我們想要從易於取得的資料序列中傳回第一個null
空項。但是,如果用於獲取我們的值的方法的評估成本很高怎麼辦?相反,出於效能原因,我們可能希望延遲計算連續的方法鏈,直到獲得第一個非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();
}
透過 Stream 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()
條件檢查,一旦找到滿足條件的第一個非null
元素,流就會終止。
5. 外部庫
我們還可以利用流行的外部函式庫來解決該問題,而不是編寫自己的實作。
5.1. Apache 通用語言 3
要使用Apache Commons Lang 3 ,我們需要將以下相依性加入pom.xml
中:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<em><version>3.13.0</version></em>
</dependency>
給定一個可能為null,
我們可以使用ObjectUtils.getIfNull()
來取得非null
值,或從延遲計算的替代方法傳回該值:
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()
延遲評估可為 null 的方法:
ObjectUtils.getFirstNonNull(this::methodA, this::methodB, this::methodC);
5.2.谷歌番石榴
要使用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
,則拋出NullPointerExecption
。
給定一個列表,我們可以使用Iterables.find()
和Predicates.nonNull():
傳回第一個非null
:
@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
。
因此,我們先前的「返回第一個非null
值」問題轉變為一個略有不同的問題。我們如何返回第一個非空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
是否有一個值。如果一個元素滿足這個謂詞,那麼我們可以使用get()
作為map()
中間運算的一部分安全地取得該值。
七、結論
在本文中,我們探討如何使用我們自己的實作以及外部函式庫傳回第一個非null
。
當我們想要從一系列昂貴的方法中傳回第一個非null
時,我們也考慮了延遲計算。
最後,我們還演示瞭如何傳回第一個非空Optional
,因為自 Java 8 中引入以來,這可能是更合適的用例。
本文中使用的程式碼範例可以在 GitHub 上找到。