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);
我们必须添加一个 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);
添加块破坏了易于阅读的管道的目的。
为了恢复可读性,我们需要重构代码以引入一个新类。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;
}
}
}
Project Lombok 是一个编译时注释处理器,可生成额外的字节码。一个人使用正确的注释并获得结果,而无需编写样板代码。
Project Lombok 是一个 Java 库,可自动插入您的编辑器和构建工具,为您的 Java 增添趣味。永远不要再编写另一个 getter 或 equals 方法,使用一个注释,您的类就有一个功能齐全的构建器、自动化您的日志记录变量等等。
-龙目岛计划
Lombok 提供了@SneakyThrow注解:它允许人们抛出已检查的异常,而无需在自己的方法签名中声明它们。然而,它目前不适用于现有的 API。
如果您是 Lombok 用户,请注意有一个已打开的 GitHub 问题,其状态为停放。
Apache Commons Lang是一个古老的项目。它在当时很普遍,因为它提供的实用程序可能是 Java API 的一部分,但不是。这是一个比在每个项目中重新发明你的DateUtils和更好的选择。StringUtils在研究这篇文章时,我发现它仍然定期使用很棒的 API 进行维护。其中之一是FailableAPI。
API 由两部分组成:
这是一个小摘录:
代码终于变成了我们一开始就期待的样子:
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
为了实现这一点,我们可以利用 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());
到目前为止,我们还停留在 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
Java 的初始设计大量使用了检查异常。编程语言的发展证明这不是一个好主意。
Java 流不能很好地处理已检查的异常。将后者集成到前者所需的代码看起来不太好。为了恢复我们期望的流的可读性,我们可以依赖 Apache Commons Lang。
汇编只代表了问题的一小部分。我们通常希望对异常采取行动,而不是停止管道或忽略异常。在这种情况下,我们可以利用 Vavr 库,它提供了一种更实用的方法。
你可以在GitHub上找到这篇文章的源代码。