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

大数据 java 代码示例

这是称为“ Functional Java by Example”的系列文章的第7部分。

我在本系列的每个部分中开发的示例是某种“提要处理程序”,用于处理文档。 之前我们已经处理过特殊情况,但是我们将在功能上将它们作为数据来处理,更多。

如果您是第一次来,最好是从头开始阅读。 它有助于了解我们从何处开始以及如何在整个系列中继续前进。

这些都是这些部分:

我将在每篇文章发表时更新链接。 如果您通过内容联合组织来阅读本文,请查看我博客上的原始文章。

每次代码也被推送到这个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()获得结果, bunchOfWork()在成功过程中因other而失败。

此类实际上是一个接口,并且有一堆默认方法,它们都返回实例本身,从而可以无限地链接广告 ,例如:

  • andFinally –提供最终尝试行为,无论操作结果如何。
  • andThen –如果成功,则运行给定的runnable,否则返回此Failure。
  • 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 ->
  // ...
}

从表面上看,似乎没有太大变化。 它只是将一组方法调用替换为其他方法调用,在这种情况下,也就足够了。

但是,您可以选择“ Try CompletableFuture因为它似乎更自然地适合我们要实现的目标-计算没有“未来主义”,也没有计划或“在某个时间点”可用。

但是还有更多。

从故障中恢复

现在我们得到的是,如果资源创建者API失败,则将任何失败很好地包装在Try ,因此我们可以轻松地遵循成功或失败的路径。

但是,如果某些故障对我们有意义 ,并且在某些情况下我们希望以其他方式失败的情况仍然成功,该怎么办?

好吧,我们可以从失败中恢复过来 ,并按照自己的意愿修改代码。 我们可以使用下面的Try方法,并使用漂亮的方法签名,称为recover(Class exception, Function f)

它的Javadoc读为:

如果是成功或失败,并且原因不能从cause.getClass()分配,则返回此值。 否则,尝试使用f恢复失败的异常,即调用Try.of(()-> f.apply((X)getCause())。

换句话说:对于特定类型的异常,我们可以提供将失败重新变为成功的功能。

首先,再次删除多余的日志记录和onSuccess/onFailure 。 现在,我们有一个Try ,一个成功场景的map和一个getOrElseGet ,错误场景的map

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 one
    return 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 one
    return repository.findById(alreadyProcessed.getApiId())
  }

  private Resource handleSpecial(SpecialException e) {
    // handle special situation
    return 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 代码示例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值