从 Java 语言的角度来看,重新抛出已检查异常的技巧是如何工作的?

How does trick with rethrow checked exceptions works from the point of view of Java language?

我遇到过我必须在流表达式中捕获所有已检查的异常。 我看过很热门的话题:

How can I throw CHECKED exceptions from inside Java 8 streams?
并且有答案建议采用以下方法:
我们有 3 个字符串: "java.lang.Object" "java.lang.Integer" "java.lang.Strin"

我们要按名称加载 类。
因此我们需要使用Class#forName方法
Class#forName java doc)

如您所见,以下方法声明包含

throws ClassNotFoundException

因此,如果我们使用通常的循环,我们需要编写以下代码:

public void foo() throws ClassNotFoundException {
    String arr[] = new String[]{"java.lang.Object", "java.lang.Integer", "java.lang.Strin"};
    for(String className:arr){
        Class.forName(className);
    }
}

让我们尝试使用流重写它:

 public void foo() throws ClassNotFoundException {
        String arr[] = new String[]{"java.lang.Object", "java.lang.Integer", "java.lang.Strin"};
        Stream.of(arr).forEach(Class::forName);
    }

编译器说:

Error:(46, 32) java: incompatible thrown types java.lang.ClassNotFoundException in method reference

好的,让我们尝试使用上述主题中的方法:

我们创建以下方法:

public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) {
    return t -> {
        try {
            consumer.accept(t);
        } catch (Exception exception) {
            throwAsUnchecked(exception);
        }
    };
}

 @FunctionalInterface
 public interface Consumer_WithExceptions<T, E extends Exception> {
     void accept(T t) throws E;
 }

@SuppressWarnings("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {
    throw (E) exception;
}

客户端代码如下所示:

public void foo() throws ClassNotFoundException {
        String arr[] = new String[]{"java.lang.Object", "java.lang.Integer", "java.lang.Strin"};
        Stream.of(arr).forEach(rethrowConsumer(Class::forName));
    }

我想知道它是如何工作的。我真的不清楚。

让我们研究 rethrowConsumer:

public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) {
    return t -> {
        try {
            consumer.accept(t);
        } catch (Exception exception) {
            throwAsUnchecked(exception);
        }
    };
}

和 throwAsUnchecked 签名看起来很像

private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E {

请澄清一下。

P.S.

我将魔术发生在 throwAsUnchecked 内部进行了本地化,因为下面的代码片段是正确的

   public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) {
        return t -> {
            try {
                throw new Exception();
            } catch (Exception exception) {
                throwAsUnchecked(exception);
            }
        };
    }

但是下面没有

public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) {
    return t -> {
        try {
            consumer.accept(t);
        } catch (Exception exception) {
            throw new Exception();
            //throwAsUnchecked(exception);
        }
    };
}

Java 编译器理解 Class.forName() 抛出 checked 异常。这需要以某种方式得到处理。但是当 catching 那个 checked 异常并让它看起来像是被包装成一个 RuntimeException 例如 - 你解决了 编译问题。通过去掉 编译器抱怨的 "thing"。

请注意:编译器理解调用throwAsUnchecked(exception)总是抛出。换句话说:通过在此处调用 method,实际上 no 方法体内的代码 must 扔。当然这不会改变运行时情况。最后,还是抛出了一个异常——令人惊讶的不是 RuntimeException,而是 ClassNotFoundException 的一个实例。

核心点是<E extends Throwable>翻译成RuntimeException(对于编译器)。那部分解释 here!

因此,foo() 的第二个版本可以这样写:

public void foo() { ...

不再需要声明throws ClassNotFoundException

换句话说:"tricking" 编译器认为抛出的异常是 未经检查的 异常。而这个技巧本身就植根于 generics/type-erasure.