为什么 Java 1.8 中的功能接口(功能、供应商、消费者等)不抛出通用异常?
Why functional interfaces in Java 1.8 (Function, Supplier, Consumer etc) do not throw generic Exception?
我必须定义自己的可抛出函数接口,例如
@FunctionalInterface
public interface ConsumerEx<T, E extends Exception> {
public void accept(T t) throws E;
public default ConsumerEx<T, E> andThen(ConsumerEx<? super T, ? extends E> after) {
return t -> {
accept(t);
after.accept(t);
};
}
}
但它不能与 forEach 一起使用:-(
功能接口是通用接口。但是具有通用异常的接口只能在特殊的上下文中工作,在这种情况下,您将一个实例传递给一个方法,该方法将立即调用该函数并重新抛出异常。
但是,它在函数未立即调用的任何上下文中不起作用,例如当它在不同的线程中或稍后执行时。
你这个特殊的消费者就是这样一个不可行的场景。您不能编写一个普通的 Consumer
委托给 ConsumerEx
实例并准确捕获为特定 ConsumerEx
实例声明的异常。由于类型擦除,你不知道 E
的确切类型。尝试将捕获的异常包装在专门的包装器 RuntimeException
中会更加困难。包装器异常必须是类型参数匹配 E
的通用异常,但您无法捕获具有类型参数的通用异常。
如果功能接口允许通用异常类型,这些问题与 JRE 开发人员在 parallel
流执行时将面临的问题完全相同。终端操作不可能保证只重新抛出声明的已检查通用异常。
如果允许异常,那么这会大大增加代码的复杂性,因为任何事情都可能引发异常。这使您的代码更难推理、编写和阅读。异常在被捕获并采取行动时也会破坏引用透明性。这样会造成不好的后果,还有更好的办法。
鉴于额外的复杂性,Java 选择不允许在您的 lambda 表达式中出现异常。
那么函数式编程是如何处理这些情况的呢?我们需要一个具有预期值或包含异常的数据结构。这些通常由具有左值(error/exception)或预期(正确)右值(右作为正确的助记符)的 Either 数据结构处理。这称为右偏 Either,因为期望正确的值包含正确的值。所以我们需要将抛出异常的方法转换为 return 一个 Either.
的函数
FunctionalJava 的 Try 接口系列 (https://functionaljava.ci.cloudbees.com/job/master/javadoc/) 就是一个例子。以消费者为例,我们复用Try0
接口
public interface Try0<A, Z extends Exception> {
A f() throws Z;
}
然后将其转换为惰性右偏 (fj.data.Validation):
list.forEach(Try.f(() -> methodWithException())._1())
我们现在可以对异常采取行动,也可以直接忽略它。
我必须定义自己的可抛出函数接口,例如
@FunctionalInterface
public interface ConsumerEx<T, E extends Exception> {
public void accept(T t) throws E;
public default ConsumerEx<T, E> andThen(ConsumerEx<? super T, ? extends E> after) {
return t -> {
accept(t);
after.accept(t);
};
}
}
但它不能与 forEach 一起使用:-(
功能接口是通用接口。但是具有通用异常的接口只能在特殊的上下文中工作,在这种情况下,您将一个实例传递给一个方法,该方法将立即调用该函数并重新抛出异常。
但是,它在函数未立即调用的任何上下文中不起作用,例如当它在不同的线程中或稍后执行时。
你这个特殊的消费者就是这样一个不可行的场景。您不能编写一个普通的 Consumer
委托给 ConsumerEx
实例并准确捕获为特定 ConsumerEx
实例声明的异常。由于类型擦除,你不知道 E
的确切类型。尝试将捕获的异常包装在专门的包装器 RuntimeException
中会更加困难。包装器异常必须是类型参数匹配 E
的通用异常,但您无法捕获具有类型参数的通用异常。
如果功能接口允许通用异常类型,这些问题与 JRE 开发人员在 parallel
流执行时将面临的问题完全相同。终端操作不可能保证只重新抛出声明的已检查通用异常。
如果允许异常,那么这会大大增加代码的复杂性,因为任何事情都可能引发异常。这使您的代码更难推理、编写和阅读。异常在被捕获并采取行动时也会破坏引用透明性。这样会造成不好的后果,还有更好的办法。
鉴于额外的复杂性,Java 选择不允许在您的 lambda 表达式中出现异常。
那么函数式编程是如何处理这些情况的呢?我们需要一个具有预期值或包含异常的数据结构。这些通常由具有左值(error/exception)或预期(正确)右值(右作为正确的助记符)的 Either 数据结构处理。这称为右偏 Either,因为期望正确的值包含正确的值。所以我们需要将抛出异常的方法转换为 return 一个 Either.
的函数FunctionalJava 的 Try 接口系列 (https://functionaljava.ci.cloudbees.com/job/master/javadoc/) 就是一个例子。以消费者为例,我们复用Try0
接口
public interface Try0<A, Z extends Exception> {
A f() throws Z;
}
然后将其转换为惰性右偏 (fj.data.Validation):
list.forEach(Try.f(() -> methodWithException())._1())
我们现在可以对异常采取行动,也可以直接忽略它。