这是称为“ 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)中,这使我们可以链接方法调用,例如thenApplyexceptionally 。 在第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/exceptionallymap/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部分–将失败也视为数据相关推荐

  1. 大数据 java 代码示例_功能Java示例 第7部分–将失败也视为数据

    大数据 java 代码示例 这是称为" Functional Java by Example"的系列文章的第7部分. 我在本系列的每个部分中开发的示例是某种"提要处理程序 ...

  2. java 函数式编程 示例_功能Java示例 第8部分–更多纯函数

    java 函数式编程 示例 这是第8部分,该系列的最后一部分称为"示例功能Java". 我在本系列的每个部分中开发的示例是某种"提要处理程序",用于处理文档. ...

  3. 对象作为参数示例java_功能Java示例 第6部分–用作参数

    对象作为参数示例java 这是称为" Functional Java by Example"的系列文章的第6部分. 我在本系列的每个部分中开发的示例是某种"提要处理程序& ...

  4. java 示例_功能Java示例 第5部分–将I / O移到外部

    java 示例 这是称为" Functional Java by Example"的系列文章的第5部分. 在上一部分中,我们停止了对文档的变异,并返回了数据的副本. 现在,我们需要 ...

  5. java 示例_功能Java示例 第4部分–首选不变性

    java 示例 这是称为" Functional Java by Example"的系列文章的第4部分. 在上一部分中,我们讨论了一些副作用,并且我想进一步详细说明如何通过将不可变 ...

  6. java 示例_功能Java示例 第3部分–不要使用异常来控制流程

    java 示例 这是称为" Functional Java by Example"的系列文章的第3部分. 我在本系列的每个部分中开发的示例是某种"提要处理程序" ...

  7. java 示例_功能Java示例 第2部分–讲故事

    java 示例 这是称为" Functional Java by Example"的系列文章的第2部分. 我在本系列的每个部分中开发的示例是某种"提要处理程序" ...

  8. java 函数式编程 示例_功能Java示例 第1部分–从命令式到声明式

    java 函数式编程 示例 功能编程(FP)的目的是避免重新分配变量,避免可变的数据结构,避免状态并全程支持函数. 如果将功能性技术应用于日常Java代码,我们可以从FP中学到什么? 在这个名为&qu ...

  9. 功能Java示例 第8部分–更多纯函数

    这是第8部分,该系列的最后一部分称为" Functional Java by Example". 我在本系列的每个部分中发展的示例是某种"提要处理程序",用于处 ...

最新文章

  1. R语言生存分析寿命表(life table)实战案例:比较两种药物治疗感染患者的生存时间
  2. 刷新存储器的容量单位是什么_存储系统 半导体存储器
  3. 21行代码AC_HDU 5935 Car【贪心, 精度】
  4. Cloud Native Weekly|2019欧洲KubeCon成功闭幕
  5. UI控件之UITextField
  6. uva 10817 - Headmaster's Headache ( 状态压缩dp)
  7. 实心和空心哪个抗弯能力强_为什么轮胎不设计成实心的?不怕爆胎 480阅读
  8. 打包 pyqt5_基于Pyqt5的简单电影搜索工具,Python让你3秒搜电影
  9. 走进波分 -- 16.Optix OSN9800产品介绍
  10. iOS 12.0-12.1.2 完整越狱教程
  11. 京享值超8万的京东钻石用户告诉你套路是这样的
  12. 【x11-forwarding disabled解决办法】
  13. 台式电脑计算机怎么看是固态硬盘,怎么看电脑是机械硬盘还是固态硬盘
  14. 大数据:大二上期期末感悟及总结
  15. vimdiff 命令使用技巧
  16. arduino电子时钟 简易版
  17. 文献阅读-FCER1G与透明细胞癌中巨噬细胞的浸润相关并且通过调节肿瘤免疫产生不良预后
  18. 电商平台-售后模块的设计与架构
  19. 对于scanf函数的研究(详细)
  20. sip uri 与sips uri 的区别

热门文章

  1. 【2018.4.7】模拟赛之二-ssl2383 睡眠【水题】
  2. ssl1203-书的复制【dp】
  3. 【DP】【高精】幸运票 (jzoj 2122)
  4. Sentinel(二十二)之使用Nacos存储规则
  5. Hadoop生态hive(五)Hive QL数据库
  6. Spring MVC请求url无效问题思考
  7. Redis进阶之内存模型
  8. 一文告诉你 Java RMI 和 RPC 的区别
  9. Oracle入门(十四.18)之使用动态SQL
  10. 这本书强烈推荐看看!