Почему у меня нет catch для проверенного исключения для вызова, который генерирует общий?

Я работаю над небольшим помощником, который должен вызывать произвольный код (передается как лямбда). Помощник должен поймать определенные исключения и бросить их в какую-нибудь обертку. Мои "собственные" исключения не должны быть обернуты, а просто переброшены.

Я придумал этот код:

@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; 
        }
    }
}

Вышеприведенная информация дает ошибку компиляции:

Недостижимый блок catch для MyCheckedException. Это исключение никогда не выбрасывается из тела оператора try MyExceptionLogger...

Иными словами: хотя apply() определено для того, чтобы бросать некоторые X extends Throwable, я не могу поймать конкретное исключенное исключение при вызове этого метода.

Я знаю, что могу перейти к рабочему коду, поймав Throwable, чтобы использовать instanceof check - но я хотел бы понять , почему невозможно попробуйте/поймать, как описано выше.

Ответ 1

Я думаю, что есть - на основе JSL - нет веской причины, по которой вы не должны улавливать свой выборочный проверенный исключение в своем примере.

Если вы прочтете цитату из JLS

Это ошибка времени компиляции, если предложение catch может уловить проверенный класс исключений E1, и это не тот случай, когда блок try, соответствующий предложению catch, может выдать проверенный класс исключений, который является подклассом или суперклассом E1, если E1 не является Exception или суперклассом Exception.

Приложению catch должно быть разрешено проверять any Exception, если метод в соответствующем try-блоке объявляет Throwable. В вашем примере блок try может выставить проверенный класс исключений, который является подклассом или суперкласс MyCheckedException, а именно Throwable и MyCheckedException, очевидно, не является Exception или суперклассом Exception

Это можно легко проверить, удалив обобщения из приведенного выше примера и увидев, что он скомпилирован без проблем:

@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;
}

Ответ 2

Это поведение указано в разделе 11.2.3 JLS:

Это ошибка времени компиляции, если предложение catch может уловить проверенный класс исключений E1, и это не тот случай, когда блок try, соответствующий предложению catch, может выдать проверенный класс исключений, который является подклассом или суперклассом E1, если E1 не является Exception или суперклассом Exception.

MyCheckedException соответствует описанию класса E1 выше, потому что он не объявлен в общем объявлении processable.apply(), и это не Exception или один из его суперклассов. Компилятор знает только, что метод может вызывать Throwable, поэтому MyCheckedException не объявляется.