Иерархия исключений/try-multi-catch

try {
        throw new FileNotFoundException();
    } catch (IOException e) {
        e.printStackTrace();
    }
    catch (Exception e) {
        e.printStackTrace();
    }

Может ли кто-нибудь сказать мне, почему второй блок catch не рассматривается компилятором как недостижимый код? Но в следующем случае:

try {
        throw new FileNotFoundException();
    } catch (Exception e) {
        e.printStackTrace();
    }
    catch (IOException e) {
        e.printStackTrace();
    }

Второй блок catch считается недостижимым?

В конце концов, FileNotFoundException относится к IOException, так же как и к Exception.

Изменить Пожалуйста, уточните: компилятор узнает, что исключение было вызвано методом на основе предложения throws этого метода. Но он может не обязательно знать конкретный тип исключения (в рамках этого класса исключения). Поэтому, если метод генерирует исключение «A», компилятор не будет знать, является ли фактическое исключение «A» или подтипом «A», потому что это определяется только во время выполнения. Однако компилятор будет знать, что исключение типа 'X' никогда не генерируется, поэтому указание блока catch для X ошибочно. Это правильно?


person user1825770    schedule 10.12.2015    source источник
comment
Возможно, вы захотите уточнить, касается ли ваш вопрос первого или второго случая. Я думаю, что ваш вопрос касается catch (Exception e) в вашем первом случае и почему компилятор не считает его недостижимым. Но похоже, что большинство людей здесь думают, что вы спрашиваете о своем втором примере. Что это?   -  person sstan    schedule 10.12.2015
comment
Я думаю, что это достаточно ясно для всех, кто ответил. Вопрос касается обоих случаев, то есть почему в первом случае нет ошибки, а во втором случае ошибка.   -  person user1825770    schedule 10.12.2015


Ответы (3)


Компилятор не может предполагать, что единственным возможным исключением из вашего блока try будет исключение FileNotFoundException. Вот почему он не считает второй блок catch недостижимым в вашем первом примере кода.

Что, если по какой-то неизвестной причине при создании экземпляра FileNotFoundException будет выброшено RuntimeException (вполне возможно)? Что тогда?

В вашем первом примере кода это неожиданное исключение времени выполнения будет перехвачено вторым блоком catch, в то время как 1-й блок позаботится о FileNotFoundException, если он будет выброшен.

Однако в вашем втором примере кода все исключения будут перехвачены первым блоком catch, что сделает второй блок недостижимым.

ИЗМЕНИТЬ:

Чтобы лучше понять, почему блок catch(Exception e) в вашем первом коде не считается недоступным для компилятора, попробуйте следующий код и обратите внимание, что второй блок catch определенно доступен:

public class CustomIOException extends IOException {
    public CustomIOException(boolean fail) {
        if (fail) {
            throw new RuntimeException("the compiler will never know about me");
        }
    }
}

public static void main(String[] args) {
    try {
        throw new CustomIOException(true);
    } catch(IOException e) {
        System.out.println("Caught some IO exception: " + e.getMessage());
    } catch(Exception e) {
        System.out.println("Caught other exception: " + e.getMessage());
    }
}

Выход:

Поймал другое исключение: компилятор никогда не узнает обо мне

person sstan    schedule 10.12.2015

TL;DR

Компилятор считает, что FileNotFoundException() может быть не единственным выброшенным Exception.


Объяснение

JLS§11.2 .3 Проверка исключений

Компилятору Java рекомендуется выдавать предупреждение, если предложение catch может перехватывать (§11.2) проверенный класс исключений E1, а блок try, соответствующий предложению catch, может генерировать проверенный класс исключений E2, подкласс E1 и предшествующее предложение catch класса непосредственно вложенный оператор try может перехватить проверенный класс исключений E3, где E2 ‹: E3 ‹: E1.

Это означает, что если компилятор считает, что единственным исключением, которое может быть вызвано вашим блоком catch, является FileNotFoundException(), он предупредит вас о втором блоке catch. Что не так здесь.

Однако следующий код

    try{
        throw new FileNotFoundException();
    } catch (FileNotFoundException e){
        e.printStackTrace();
    } catch (IOException e){ // The compiler warns that all the Exceptions possibly 
                             // catched by IOException are already catched even though
                             // an IOException is not necessarily a FNFException
        e.printStackTrace();
    } catch (Exception e){
        e.printStackTrace();
    }

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

Поскольку компилятор не предупреждает нас о Èxception e, он считает, что могут быть выброшены другие исключения (например, RunTimeException). Поскольку обработка этих исключений RunTimeException не является задачей компилятора, он позволяет этому проскользнуть.


Остальную часть ответа интересно прочитать, чтобы понять механизм перехвата исключений.


Схема

Как видите, Exception находится выше в иерархии, поэтому его нужно объявлять последним после IOException, который находится ниже в иерархии.

введите здесь описание изображения


Пример

Представьте, что вам бросили IOException. Поскольку он унаследован от Exception, мы можем сказать, что IOException IS-A Exception, и поэтому он всегда будет перехватываться в блоке Exception, а блок IOException будет недоступен.


Пример из реальной жизни

Допустим, вы в магазине и вам нужно выбрать брюки. Продавец говорит вам, что вы должны попробовать брюки от самых больших до самых маленьких, и если вы найдете те, которые вы можете носить (даже если они не вашего размера), вы должны взять их.

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

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

Вы обнаружите, что покупаете брюки именно своего размера.

Это небольшая аналогия, немного странная, но она говорит сама за себя.


Начиная с Java 7: multi-catch< /а>

Начиная с Java 7, у вас есть возможность включить все типы исключений, которые могут быть вызваны вашим блоком try, в один единственный блок catch.

ВНИМАНИЕ: Вы также должны соблюдать иерархию, но на этот раз слева направо.

В вашем случае это будет

try{
    //doStuff
}catch(IOException | Exception e){
    e.printStackTrace();
}

Следующий пример, допустимый в Java SE 7 и более поздних версиях, устраняет дублированный код:

catch (IOException|SQLException ex) {
    logger.log(ex);
    throw ex;
}

Предложение catch указывает типы исключений, которые может обрабатывать блок, и каждый тип исключения отделяется вертикальной чертой (|).

person Yassin Hajaj    schedule 10.12.2015
comment
Это не объясняет, почему catch (Exception e) не недоступен в первом примере вопроса. - person Erwin Bolwidt; 10.12.2015
comment
@ErwinBolwidt Нет, извините, я очень хорошо понял вопрос. ОП только что отредактировал. - person Yassin Hajaj; 10.12.2015

Первый случай:

catch (IOException e) { // A specific Exception
    e.printStackTrace();
}
catch (Exception e) { // If there's any other exception, move here
    e.printStackTrace();
}

Как видите, первый IOException пойман. Это означает, что мы стремимся к одному конкретному исключению. Затем во втором улове мы стремимся к любым другим исключениям, кроме IOException. Следовательно, это логично.

Во втором:

catch (Exception e) { // Move here no matter whatever exception
    e.printStackTrace();
}
catch (IOException e) { // The block above already handles *Every exception, hence this won't be reached.
    e.printStackTrace();
}

Мы поймали любое исключение (будь то IOException или какое-то другое исключение) прямо в первом блоке. Следовательно, второй блок не будет достигнут, потому что все уже включено в первый блок.

Другими словами, в первом случае мы стремимся к какому-то конкретному исключению, чем к любым другим исключениям. В то время как во втором случае мы сначала нацеливаемся на все/любое исключение, чем на конкретное исключение. А так как мы уже обработали все исключения, наличие конкретного исключения позже не будет иметь никакого логического смысла.

person Jaskaranbir Singh    schedule 10.12.2015