为什么 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())

我们现在可以对异常采取行动,也可以直接忽略它。