<返回更多

Lambda 中的异常

2022-10-21    qaseven
加入收藏
JAVA 流不能很好地处理已检查的异常。在本文中,深入探讨如何管理此类问题。

 

Java 引入了检查异常的概念。与早期的方法相比,强制开发人员管理异常的想法是革命性的。

如今,Java 仍然是唯一一种提供检查异常的广泛使用的语言。例如,Kotlin 中的每个异常都是未经检查的。

即使在 Java 中,新特性也与受检异常不一致:Java 内置函数式接口的签名不使用异常。当将遗留代码集成到 lambda 中时,会导致代码繁琐。这在 Streams 中很明显。

在这篇文章中,我想深入探讨如何处理此类问题。

 

代码中的问题

 

下面是一个示例代码来说明这个问题:

Stream.of("java.lang.String", "ch.frankel.blog.Dummy", "java.util.ArrayList")
.map(it -> new ForNamer().Apply(it)) // 1
.forEach(System.out::println);

 

  1. 不编译:需要捕获已检查ClassNotFoundException

 

我们必须添加一个 try/catch 块来修复编译问题。

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 块封装到一个类中

 

为了恢复可读性,我们需要重构代码以引入一个新类。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;
}
}
}

 

  1. 创建单个记录对象。
  2. 重复使用它。
尝试龙目岛

 

Project Lombok 是一个编译时注释处理器,可生成额外的字节码。一个人使用正确的注释并获得结果,而无需编写样板代码。

 

Project Lombok 是一个 Java 库,可自动插入您的编辑器和构建工具,为您的 Java 增添趣味。永远不要再编写另一个 getter 或 equals 方法,使用一个注释,您的类就有一个功能齐全的构建器、自动化您的日志记录变量等等。
-龙目岛计划

 

Lombok 提供了@SneakyThrow注解:它允许人们抛出已检查的异常,而无需在自己的方法签名中声明它们。然而,它目前不适用于现有的 API。

如果您是 Lombok 用户,请注意有一个已打开的 GitHub 问题,其状态为停放。

 

Commons Lang 救援

 

Apache Commons Lang是一个古老的项目。它在当时很普遍,因为它提供的实用程序可能是 Java API 的一部分,但不是。这是一个比在每个项目中重新发明你的DateUtils和更好的选择。StringUtils在研究这篇文章时,我发现它仍然定期使用很棒的 API 进行维护。其中之一是FailableAPI。

API 由两部分组成:

 

  1. 一个包装器Stream
  2. 签名接受异常的管道方法

 

这是一个小摘录:

代码终于变成了我们一开始就期待的样子:

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);

 

修复编译时错误是不够的

 

前面的代码在运行时ClassNotFoundException抛出一个Wrapped in an 。我们满足了编译器,但我们无法指定预期的行为:UndeclaredThrowableException

 

  1. 抛出第一个异常
  2. 丢弃异常
  3. 聚合类和异常,以便我们可以在管道的最后阶段对它们采取行动
  4. 别的东西

 

为了实现这一点,我们可以利用 Vavr 的力量。Vavr 是一个将函数式编程的强大功能带入 Java 语言的库:

 

Vavr 核心是 Java 的函数库。它有助于减少代码量并增加健壮性。函数式编程的第一步是开始思考不可变的值。Vavr 提供了不可变的集合以及对这些值进行操作所需的函数和控制结构。结果很漂亮,而且很有效。
- Vavr

 

想象一下,我们想要一个收集异常和类的管道。以下是描述几个构建块的 API 摘录:

它转换为以下代码:

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());

  1. 将调用包装到 VavrTry中。
  2. 将 转换Try为Either以保留异常。如果我们不感兴趣,我们可以使用 anOptional来代替。
  3. 根据是否Either包含异常left或预期结果right 采取行动。

 

到目前为止,我们还停留在 Java Streams 的世界中。它按预期工作,直到forEach看起来并不“好”。

Vavr 确实提供了自己的Stream类,它模仿 Java StreamAPI 并添加了额外的特性。让我们用它来重写管道:

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

 

  1. Stream将of划分Either为两个的元组Stream。
  2. 将左侧流从 a Streamof展平Either到 a Streamof Throwable。
  3. 将右流从 a Streamof展平Either到 a Streamof Class。
  4. 做我们想做的任何事。
结论

Java 的初始设计大量使用了检查异常。编程语言的发展证明这不是一个好主意。

Java 流不能很好地处理已检查的异常。将后者集成到前者所需的代码看起来不太好。为了恢复我们期望的流的可读性,我们可以依赖 Apache Commons Lang。

汇编只代表了问题的一小部分。我们通常希望对异常采取行动,而不是停止管道或忽略异常。在这种情况下,我们可以利用 Vavr 库,它提供了一种更实用的方法。

你可以在GitHub上找到这篇文章的源代码。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>