JAVA 流不能很好地處理已檢查的異常。在本文中,深入探討如何管理此類問題。
Java 引入了檢查異常的概念。與早期的方法相比,強(qiáng)制開發(fā)人員管理異常的想法是革命性的。
如今,Java 仍然是唯一一種提供檢查異常的廣泛使用的語(yǔ)言。例如,Kotlin 中的每個(gè)異常都是未經(jīng)檢查的。
即使在 Java 中,新特性也與受檢異常不一致:Java 內(nèi)置函數(shù)式接口的簽名不使用異常。當(dāng)將遺留代碼集成到 lambda 中時(shí),會(huì)導(dǎo)致代碼繁瑣。這在 Streams 中很明顯。
在這篇文章中,我想深入探討如何處理此類問題。
代碼中的問題
下面是一個(gè)示例代碼來(lái)說(shuō)明這個(gè)問題:
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
.map(it -> new ForNamer().Apply(it)) // 1
.forEach(System.out::println);
- 不編譯:需要捕獲已檢查ClassNotFoundException
我們必須添加一個(gè) try/catch 塊來(lái)修復(fù)編譯問題。
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
.map(it -> {
try {
return Class.forName(it);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
})
.forEach(System.out::println);
添加塊破壞了易于閱讀的管道的目的。
將 Try/Catch 塊封裝到一個(gè)類中
為了恢復(fù)可讀性,我們需要重構(gòu)代碼以引入一個(gè)新類。IntelliJ IDEA 甚至提出了一條記錄:
var forNamer = new ForNamer(); // 1
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
.map(forNamer::apply) // 2
.forEach(System.out::println);
record ForNamer() implements Function> {
@Override
public Class apply(String string) {
try {
return Class.forName(string);
} catch (ClassNotFoundException e) {
return null;
}
}
}
- 創(chuàng)建單個(gè)記錄對(duì)象。
- 重復(fù)使用它。
嘗試龍目島
Project Lombok 是一個(gè)編譯時(shí)注釋處理器,可生成額外的字節(jié)碼。一個(gè)人使用正確的注釋并獲得結(jié)果,而無(wú)需編寫樣板代碼。
Project Lombok 是一個(gè) Java 庫(kù),可自動(dòng)插入您的編輯器和構(gòu)建工具,為您的 Java 增添趣味。永遠(yuǎn)不要再編寫另一個(gè) getter 或 equals 方法,使用一個(gè)注釋,您的類就有一個(gè)功能齊全的構(gòu)建器、自動(dòng)化您的日志記錄變量等等。
-龍目島計(jì)劃
Lombok 提供了@SneakyThrow注解:它允許人們拋出已檢查的異常,而無(wú)需在自己的方法簽名中聲明它們。然而,它目前不適用于現(xiàn)有的 API。
如果您是 Lombok 用戶,請(qǐng)注意有一個(gè)已打開的 GitHub 問題,其狀態(tài)為停放。
Commons Lang 救援
Apache Commons Lang是一個(gè)古老的項(xiàng)目。它在當(dāng)時(shí)很普遍,因?yàn)樗峁┑膶?shí)用程序可能是 Java API 的一部分,但不是。這是一個(gè)比在每個(gè)項(xiàng)目中重新發(fā)明你的DateUtils和更好的選擇。StringUtils在研究這篇文章時(shí),我發(fā)現(xiàn)它仍然定期使用很棒的 API 進(jìn)行維護(hù)。其中之一是FailableAPI。
API 由兩部分組成:
- 一個(gè)包裝器Stream
- 簽名接受異常的管道方法
這是一個(gè)小摘錄:
代碼終于變成了我們一開始就期待的樣子:
Stream stream = Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList");
Failable.stream(stream)
.map(Class::forName) // 1
.forEach(System.out::println);
修復(fù)編譯時(shí)錯(cuò)誤是不夠的
前面的代碼在運(yùn)行時(shí)ClassNotFoundException拋出一個(gè)Wrapped in an 。我們滿足了編譯器,但我們無(wú)法指定預(yù)期的行為:UndeclaredThrowableException
- 拋出第一個(gè)異常
- 丟棄異常
- 聚合類和異常,以便我們可以在管道的最后階段對(duì)它們采取行動(dòng)
- 別的東西
為了實(shí)現(xiàn)這一點(diǎn),我們可以利用 Vavr 的力量。Vavr 是一個(gè)將函數(shù)式編程的強(qiáng)大功能帶入 Java 語(yǔ)言的庫(kù):
Vavr 核心是 Java 的函數(shù)庫(kù)。它有助于減少代碼量并增加健壯性。函數(shù)式編程的第一步是開始思考不可變的值。Vavr 提供了不可變的集合以及對(duì)這些值進(jìn)行操作所需的函數(shù)和控制結(jié)構(gòu)。結(jié)果很漂亮,而且很有效。
- Vavr
想象一下,我們想要一個(gè)收集異常和類的管道。以下是描述幾個(gè)構(gòu)建塊的 API 摘錄:
它轉(zhuǎn)換為以下代碼:
Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
.map(CheckedFunction1.liftTry(Class::forName)) // 1
.map(Try::toEither) // 2
.forEach(e -> {
if (e.isLeft()) { // 3
System.out.println("not found:" + e.getLeft().getMessage());
} else {
System.out.println("class:" + e.get().getName());
- 將調(diào)用包裝到 VavrTry中。
- 將 轉(zhuǎn)換Try為Either以保留異常。如果我們不感興趣,我們可以使用 anOptional來(lái)代替。
- 根據(jù)是否Either包含異常left或預(yù)期結(jié)果right 采取行動(dòng)。
到目前為止,我們還停留在 Java Streams 的世界中。它按預(yù)期工作,直到forEach看起來(lái)并不“好”。
Vavr 確實(shí)提供了自己的Stream類,它模仿 Java StreamAPI 并添加了額外的特性。讓我們用它來(lái)重寫管道:
var result = Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
.map(CheckedFunction1.liftTry(Class::forName))
.map(Try::toEither)
.partition(Either::isLeft) // 1
.map1(left -> left.map(Either::getLeft)) // 2
.map2(right -> right.map(Either::get)); // 3
result._1().forEach(it -> System.out.println("not found: " + it.getMessage())); // 4
result._2().forEach(it -> System.out.println("class: " + it.getName())); // 4
- Stream將of劃分Either為兩個(gè)的元組Stream。
- 將左側(cè)流從 a Streamof展平Either到 a Streamof Throwable。
- 將右流從 a Streamof展平Either到 a Streamof Class。
- 做我們想做的任何事。
結(jié)論
Java 的初始設(shè)計(jì)大量使用了檢查異常。編程語(yǔ)言的發(fā)展證明這不是一個(gè)好主意。
Java 流不能很好地處理已檢查的異常。將后者集成到前者所需的代碼看起來(lái)不太好。為了恢復(fù)我們期望的流的可讀性,我們可以依賴 Apache Commons Lang。
匯編只代表了問題的一小部分。我們通常希望對(duì)異常采取行動(dòng),而不是停止管道或忽略異常。在這種情況下,我們可以利用 Vavr 庫(kù),它提供了一種更實(shí)用的方法。
你可以在GitHub上找到這篇文章的源代碼。