为什么我不能对引发泛型的调用的已检查异常进行捕获?

我正在研究一个小帮助程序,它应该调用任意代码(作为lambda传入)。帮助程序应捕获某些异常,并将它们放入某个包装器中。我的“自己的”异常不应该被包装,而只是重新抛出。

我想出了这个代码:

@FunctionalInterface
interface Processable<T, X extends Throwable> {
    public T apply() throws X;
}

class MyCheckedException extends Exception { ... }
class MyCheckedExceptionWrapper extends MyCheckedException { ... }

public class MyExceptionLogger<T, X extends Throwable> {
    public T process(Processable<T, X> processable) throws MyCheckedException {
        try {
            return processable.apply();
        } catch (MyCheckedException thrown) { // this line isn't accepted
            throw thrown;
        } catch (Exception | LinkageError thrown) {
            throw new MyCheckedExceptionWrapper(thrown);
        } catch (Throwable thrown) {
           ... just log
          return null; 
        }
    }
}

上面给出了一个编译错误:

MyCheckedException 的无法访问的捕获块。此异常永远不会从 try 语句正文 MyExceptionLogger 中引发...

换句话说:虽然被定义为抛出一些,但在调用该方法时,我无法捕获特定的已检查异常。apply()X extends Throwable

我知道我可以通过捕获Shrewable来开始工作代码,然后使用检查 - 但我想了解为什么不可能像上面概述的那样进行尝试/捕获。instanceof


答案 1

此行为在 JLS 的 11.2.3 节中指定:

如果 catch 子句可以捕获已检查的异常类,并且与 catch 子句对应的 try 块不能引发已检查的异常类,该类是 的子类或超类,则这是编译时错误,除非 是 或 是 的超类。E1E1E1ExceptionException

MyCheckedException符合上述类的描述,因为它不是 在 的泛型声明中声明的,并且它不是或它的超类之一。编译器只知道该方法可以抛出一个 ,所以没有声明。E1processable.apply()ExceptionThrowableMyCheckedException


答案 2

我认为 - 基于JSL - 没有充分的理由说明为什么你不应该在你的示例中捕获你的自定义检查异常。

如果您阅读JLS的引用

如果 catch 子句可以捕获已检查的异常类,并且与 catch 子句对应的 try 块不能引发已检查的异常类,该类是 的子类或超类,则这是编译时错误,除非 是 或 是 的超类。E1E1E1ExceptionException

如果相应 try-block 中的方法声明 ,则应允许 catch-子句捕获任何选中的方法。在您的示例中,try-block 可以引发一个已检查的异常类,该类是 MyCheckedException 的子类或超类,显然不是 ExceptionException 的超类ExceptionThrowableThrowableMyCheckedException

这可以通过从上面的示例中删除泛型来轻松验证,并看到它编译没有问题:

@FunctionalInterface
interface Processable<T> {
    public T apply() throws Throwable;
}


private <T> T process(Processable<T> aProcessable) {
    try {
        return aProcessable.apply();
    } catch (MyCheckedException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } catch (Throwable e) {
        e.printStackTrace();
    }

    return null;
}

也就是说,这个问题必须以某种方式与泛型与例外的使用有关。也许这与类型擦除有关,但是对于擦除的类型,您的示例也可以正常工作:

@FunctionalInterface
interface Processable {
    public Object apply() throws Throwable;
}


private Object process(Processable aProcessable) {
    try {
        return aProcessable.apply();
    } catch (MyCheckedException e) {
        e.printStackTrace();
    } catch (Exception e) {
        e.printStackTrace();
    } catch (Throwable e) {
        e.printStackTrace();
    }

    return null;
}

推荐