功能Java示例 第7部分–将失败也视为数据
這是稱為“ Functional Java by Example”的系列文章的第7部分。
我在本系列的每個部分中發展的示例是某種“提要處理程序”,用于處理文檔。 之前我們已經處理過特殊情況,但我們將在功能上將它們作為數據來處理,更多。
如果您是第一次來,最好是從頭開始閱讀。 它有助于了解我們從何處開始以及如何在整個系列中繼續前進。
這些都是這些部分:
- 第1部分–從命令式到聲明式
- 第2部分–講故事
- 第3部分–不要使用異常來控制流程
- 第4部分–首選不變性
- 第5部分–將I / O移到外部
- 第6部分–用作參數
- 第7部分–將失敗也視為數據
- 第8部分–更多純函數
我將在每篇文章發表時更新鏈接。 如果您通過內容聯合組織來閱讀本文,請查看我博客上的原始文章。
每次代碼也被推送到這個GitHub項目 。
優雅失敗:回顧不多
這是我們以前留下的東西:
class FeedHandler {List handle(List changes,Function creator) {changes.findAll { doc -> isImportant(doc) }.collect { doc ->creator.apply(doc).thenApply { resource ->setToProcessed(doc, resource)}.exceptionally { e ->setToFailed(doc, e)}.get()}}private static boolean isImportant(doc) {doc.type == 'important'}private static Doc setToProcessed(doc, resource) {doc.copyWith(status: 'processed',apiId: resource.id)}private static Doc setToFailed(doc, e) {doc.copyWith(status: 'failed',error: e.message)}}提要處理程序的主要職責是“處理”已更改文檔的列表,這似乎是每次在文檔中創建“資源”并進一步處理時。
在上一部分中,它已抽象為一個函數,該函數接受Doc并返回Resource ,在Java中如下所示: Function creator
您可以看到資源實際上包裝在CompletableFuture (CF)中,這使我們可以鏈接方法調用,例如thenApply和exceptionally 。 在第3部分(不要使用異常來控制流程) ,我們推出了exceptionally更換,我們使用了部分try-catch來應對可能的例外創建資源時。
當時的代碼如下:
try {def resource = createResource(doc)updateToProcessed(doc, resource) } catch (e) {updateToFailed(doc, e) }我們將其替換為:
createResource(doc) .thenAccept { resource ->updateToProcessed(doc, resource) }.exceptionally { e ->updateToFailed(doc, e) }CF使我們可以發出“異常”完成的信號,而無需使用諸如拋出Exception類的副作用。 在Java SDK中,這是封裝結果(成功或失敗)并與(例如) Optional (當前值或空值)共享monadic屬性的少數幾個類之一。
在其他語言(例如Scala)中,有一種專用類型稱為Try 。
嘗試
從Scala試用文檔:
Try類型表示可能導致異常或返回成功計算值的計算。
使用Try Scala開發人員無需在可能發生異常的任何地方進行顯式異常處理。 如果我們也要在Java中使用它呢?
幸運的是,有一個名為Vavr的庫,其中包含我們可以在Java項目中使用的大量功能實用程序。
Vavr Try文檔中的示例向我們展示了完全忘記異常是多么容易:
Try.of(() -> bunchOfWork()).getOrElse(other);我們要么得到的結果從bunchOfWork()成功時,或other在前進的道路上發生故障時。
此類實際上是一個接口,并且有一堆默認方法,它們都返回實例本身,從而可以無限地鏈接廣告 ,例如:
- andFinally –提供最終嘗試行為,無論操作結果如何。
- andThen –如果給定的可運行對象是成功的,則運行它,否則返回此失敗。
- filter –如果是失敗或成功,并且值滿足謂詞,則返回此值。
- onFailure –如果這是一次失敗,則消耗該throwable。
- onSuccess –如果成功,則使用該值。
- map –如果給定的函數成功,則運行給定的選中函數,并將當前表達式的結果傳遞給它。
返回最終值的方法:
- get –如果成功則獲取此Try的結果,如果失敗則獲取throw。
- getCause –如果失敗則獲取原因,如果成功則拋出。
- getOrElse –返回基礎值(如果存在),否則返回另一個值。
- getOrElseGet –返回基礎值(如果存在),否則返回另一個函數的值。
- getOrElseThrow –返回基礎值(如果存在),否則拋出getOrElseThrow ()。
- getOrElseTry –返回基礎值(如果存在),否則返回Try.of(supplier).get()的結果。
- getOrNull –返回基礎值(如果存在),否則返回null 。
將庫包含在項目中后,我們的代碼有什么好處?
只需將我們的CompletableFuture替換為Try 。
因此,將我們的調用替換為thenApply/exceptionally到map/getOrElseGet '
creator.apply(doc) .thenApply { resource ->// ... }.exceptionally { e ->// ... }.get()變成
creator.apply(doc) .map { resource ->// ... }.getOrElseGet { e ->// ... }Try的map -method接受一個函數,該函數在try為“成功”時運行(如前所述)。 getOrElseGet方法可以在發生故障(例如異常)的情況下接受函數。
您可以像在Stream那樣窺視內部,例如
creator.apply(doc) .peek { resource ->println "We've got a $resource" } .map { resource ->// ... }.getOrElseGet { e ->// ... }或者您可以添加更多日志記錄以進行開發或故障排除,例如
creator.apply(doc) .peek { resource ->println "We've got a $resource" }.onSuccess { resource ->println "Successfully created $resource" }.onFailure { e ->println "Bugger! Got a $e" }.map { resource ->// ... }.onSuccess { document ->println "Successfully processed $document" }.onFailure { e ->println "Bugger! Processing failed with $e" }.getOrElseGet { e ->// ... }從表面上看,似乎沒有太大變化。 它只是將一組方法調用替換為其他方法調用,在這種情況下,也就足夠了。
但是,您可以選擇“對CompletableFuture Try ,因為它似乎更自然地適合我們要實現的目標-我們的計算沒有“未來主義”,也沒有計劃或“在某個時間點”可用。
但是還有更多。
從故障中恢復
現在我們得到的是,如果資源創建者API失敗,則將任何失敗都很好地包裝在Try ,因此我們可以輕松地遵循成功或失敗的路徑。
但是,如果某些失敗對我們有意義 ,并且在某些情況下,我們希望以其他方式失敗的情況仍然能夠成功怎么辦?
好吧,我們可以從失敗中恢復過來 ,并按照自己的意愿修改代碼。 我們可以使用下面的Try方法,并使用漂亮的方法簽名,稱為recover(Class exception, Function f) 。
它的Javadoc讀為:
如果是成功或失敗,并且原因不能從cause.getClass()分配,則返回此值。 否則,嘗試使用f恢復失敗的異常,即調用Try.of(()-> f.apply((X)getCause())。
換句話說:對于特定類型的異常,我們可以提供一個函數,它將使失敗再次變為成功。
首先,再次刪除多余的日志記錄和onSuccess/onFailure 。 現在,我們有一個Try ,一個成功場景的map ,以及一個用于錯誤場景的getOrElseGet :
class FeedHandler {List handle(List changes,Function creator) {changes.findAll { doc -> isImportant(doc) }.collect { doc ->creator.apply(doc).map { resource ->setToProcessed(doc, resource)}.getOrElseGet { e ->setToFailed(doc, e)}}}// ...}如果“資源創建” API(即creator#apply調用)拋出異常(例如, DuplicateResourceException ),該信號表明我們正在創建的資源是重復項 ,該副本 已經存在 ,該怎么辦?
我們可以使用recover功能!
List handle(List changes,Function creator) {changes.findAll { doc -> isImportant(doc) }.collect { doc ->creator.apply(doc).recover { t ->handleDuplicate(doc)}.map { resource ->setToProcessed(doc, resource)}.getOrElseGet { e ->setToFailed(doc, e)}}}private Resource handleDuplicate(Doc alreadyProcessed) {// find earlier saved, existing resource and return that onereturn repository.findById(alreadyProcessed.getApiId())}我們可以在自己的端查找一個重復項(因為它已經被處理過一次),所以我們的“ handleDuplicate”方法將返回任何令人滿意的流程 (即Resource ),并且繼續處理就好像什么都沒有發生一樣。
當然,這只是一個示例,但是recover接受任何接受Throwable并再次返回Try函數。
多種故障:模式匹配
- 如果我們實際上需要確保僅在DuplicateResourceException情況下僅處理“重復”情況,而不是像現在這樣,僅處理任何異常,該怎么辦?
- 如果API可能引發我們還需要專門處理的另一種類型的異常該怎么辦? 我們如何在處理多個異常類型的“選擇”之間進行選擇?
這是使用Vavr的Match API進行模式匹配的地方。 我們可以創建一個Match的異常對象x (通過給使用recover ),同時給予靜態of -方法幾種情況選擇。
recover { x -> Match(x).of(Case($(instanceOf(DuplicateResourceException.class)), t -> handleDuplicate(doc)),Case($(instanceOf(SpecialException.class)), t -> handleSpecial(t)) )}這個$實際上是Vavr的靜態方法,有幾種重載的版本返回一個模式 。
這里的版本是接受Predicate的所謂“保護模式”。 從Vavr Javadocs(使用純Java)中查看另一個示例:
String evenOrOdd(int num) {return Match(num).of(Case($(i -> i % 2 == 0), "even"),Case($(this::isOdd), "odd")); }boolean isOdd(int i) {return i % 2 == 1; }函數的組合( Case , $和Match )在Java中似乎有些奇怪,但是目前還沒有本機支持。 同時,您可以將Vavr用于此類功能。
在Java 12中,已經有兩個預覽功能正在努力使所有這些變為現實。 JEP 305:instanceof的模式匹配和JEP 325:開關表達式
在本期文章中,我們已經看到我們可以將故障用作數據,例如,可以走一條替代路徑并返回到功能流程。
作為參考,代碼現在看起來如下:
class FeedHandler {List<Doc> handle(List<Doc> changes,Function<Doc, Try<Resource>> creator) {changes.findAll { doc -> isImportant(doc) }.collect { doc ->creator.apply(doc).recover { x -> Match(x).of(Case($(instanceOf(DuplicateResourceException.class)), t -> handleDuplicate(doc)),Case($(instanceOf(SpecialException.class)), t -> handleSpecial(t)))}.map { resource ->setToProcessed(doc, resource)}.getOrElseGet { e ->setToFailed(doc, e)}}}private Resource handleDuplicate(Doc alreadyProcessed) {// find earlier saved, existing resource and return that onereturn repository.findById(alreadyProcessed.getApiId())}private Resource handleSpecial(SpecialException e) {// handle special situationreturn new Resource()}private static boolean isImportant(doc) {doc.type == 'important'}private static Doc setToProcessed(doc, resource) {doc.copyWith(status: 'processed',apiId: resource.id)}private static Doc setToFailed(doc, e) {doc.copyWith(status: 'failed',error: e.message)}}由于Groovy 2.x解析器無法正確理解lambda語法,因此上述示例在GitHub上的示例實際上無法正確解析為Groovy,但是當然您也可以找到等效的有效Java版本 。
繼續,自己Try 。
下次,我們將以更多功能結束本系列!
如果您有任何意見或建議,我很想聽聽他們的意見!
翻譯自: https://www.javacodegeeks.com/2019/05/functional-java-by-example-treat-failures-data-too.html
總結
以上是生活随笔為你收集整理的功能Java示例 第7部分–将失败也视为数据的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 罗非鱼最新消息(罗非鱼备案)
- 下一篇: 如何在Java中使用Zxing和JFre
