Каковы правила перехвата исключений в блоках try catch в Java? - PullRequest
0 голосов
/ 17 февраля 2019

Я читал спецификацию языка Java, и эта часть привлекла мое внимание:

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

Часть, которую я не понимаю, выделена жирным шрифтом выше.

Учитывая следующую иерархию

class A extends Exception {}
class B extends A {}
class C extends B {}

Вот что я понимаю:

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

void f() {
    try {
        throw new C();
    } catch (B b) {

    }
}

, который хорошо компилируется ..

Но что подразумевается под

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

?

Это то, что я понимаю, шкоторый не компилируется (я не ожидал этого, но JLS заставляет меня так поступать)

void f() {
    try {
        throw new A();
    } catch (B b) {  // will not compile

    }
}

Поскольку очень маловероятно, что спецификация неверна, вы можете помочь мне понять, что имеется в видучастью, с которой я путаюсь, предпочтительно с примером, который демонстрирует?

Ответы [ 3 ]

0 голосов
/ 17 февраля 2019

обратите внимание, что отмеченное исключение означает любой класс исключений, не расширяющий Error или RuntimeException.Это просто говорит о том, что если вы объявите в перехвате проверенное исключение, отличное от Exception или throwable, компилятор будет жаловаться, если исключение не может быть выдано блоком try.

Я полагаю, что суперкласс E1 можетссылаясь на случай, когда экземпляр E1 или подкласс E1 может быть сохранен как суперкласс E1.В этом случае компилятор может выполнить дополнительные проверки вне заявленной логики, чтобы убедиться, что экземпляр суперкласса E1 на самом деле не является E1 или подклассом E1, прежде чем он пожалуется.

Если мы говорим о спецификации языка,фактический вывод компилятора может отличаться для дальнейших проверок при условии, что вывод аналогичен конкретной ситуации.Один компилятор может фактически проверить, может ли экземпляр E1 быть брошен как суперкласс, в то время как другой может просто показать ошибку компиляции, при условии, что каждая реализация иначе придерживается спецификации.

0 голосов
/ 17 февраля 2019

Я думаю, что простой способ понять это с конкретным примером:

public void doOpen(File file) throws IOException {
    new FileInputStream(file);
}

Обратите внимание, что метод объявлен как выбрасывающий IOException, а не его подкласс FileNotFoundException.

Теперь давайте попробуем отловить исключение, которое, как мы знаем, будет сгенерировано, когда файл не существует:

try {
    doOpen(someFile);
} catch (FileNotFoundException ex) {
    // print a message
} 

JLS говорит:

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

В предложении catch в нашем примере кода E1 в JLS равно FileNotFoundException.Метод объявлен как бросающий IOException.Если бы выделенного предложения там не было (т. Е. «Или суперкласс E1»), то этот улов не был бы законным.Но очевидно, что должно быть законным, поскольку метод doOpen явно может throw FileNotFoundException.

0 голосов
/ 17 февраля 2019

Это действительно очень сложно.Чтобы понять это, вы должны вернуться к концепции наследования в объектно-ориентированной парадигме:

  • Если я хочу создать ArrayList, я мог бы сделать это двумя различными способами:
    1. Первый - игнорирование всей концепции наследования, жесткое кодирование ArrayList и установка его типа как самого ArrayList:
      ArrayList<String> list = new ArrayList<String>();
      
    2. Второй - следование концепции наследования, и мы пытаемся создать экземпляр на более высоком уровне.класс / интерфейс:
      List<String> list = new ArrayList<String>();
      

Этот второй подход лучше по многим причинам, которые я не буду упоминать, поскольку он не входит в объем ответа, но поскольку ArrayList наследуется отList, мы можем создать новый ArrayList как объект List, но сделать что-то подобное невозможно, потому что List не расширяется или не наследуется от ArrayList:

ArrayList<String> list = new List<String>();

Та же идея применима кПойманные исключения Java.Следуя вашему примеру, если у меня есть:

class A extends Exception {}
class B extends A {}
class C extends B {}

Когда я пытаюсь поймать исключение, я сначала хочу попробовать класс более низкого уровня (C) (то есть более конкретный), потому что у меня естьбольшая уверенность в том, что это значит:

try {
    // something here that throws C
} catch (C exc) {
    // Great! Its C! I know exactly what should I do!
} catch (B exc) {
    // Not that great, it might not be that hard to figure out what caused this
} catch (A exc) {
    // I still have some idea of what might have thrown this exception
} catch (Exception e) {
    // Oh boy, its not an exception that I mapped before... It might be harder than I thought... :(
}

Но мы должны следовать той же мысли, что и ArrayList, не имея возможности получить List объект в исключениях:

  • a Исключение C может быть перехвачено catch (C exc), catch (B exc), catch (A exc) и catch (Exception e)
  • a Исключение B может быть перехвачено catch (B exc), catch (A exc) и catch (Exception e)
  • a Исключение может быть поймано catch (A exc) и catch (Exception e)
  • и т. Д. ...

Так как A относится к «более высокому уровню», чемB, это не может быть поймано catch (B exc).Вот почему он не компилируется в вашем примере:

void f() {
    try {
        throw new A();
    } catch (B b) {  // will not compile

    }
}

Внутри спецификации на странице , на которую вы ссылались , прямо перед 11.3 есть фрагмент кода, который иллюстрирует то, что вы спрашивали, потому что какЯ уже объяснил, вы все правильно поняли:

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

Это означает, что вы не можете перехватить подкласс предыдущего блока catch, и javaкомпиляторы увидят это как ошибку.Например, если вы попытаетесь сделать это

try {
    throw new C();
} catch (A exc) {
    // do something here, because the exception will be caught
} catch (B exc) {
    // since the exception has been caught before and B is subclass of A,
    // this causes the compilation error that JLS was talking about:
    // you cannot catch a subclass of a class previously caught.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...