Java исключение не пойман?

170

У меня есть небольшая теоретическая проблема с конструкциями try-catch.

Вчера я сдал практический экзамен по Java, и я не понимаю следующий пример:

try {
    try {
        System.out.print("A");
        throw new Exception("1");
    } catch (Exception e) {
        System.out.print("B");
        throw new Exception("2");
    } finally {
        System.out.print("C");
        throw new Exception("3");
    }
} catch (Exception e) {
    System.out.print(e.getMessage());
}

Вопрос был "как будет выглядеть выход?"

Я был почти уверен, что это будет AB2C3, НО неожиданный сюрприз, это не правда.

Правильный ответ - ABC3 (проверено и действительно так).

Мой вопрос: куда делось исключение ("2")?

Kousalik
источник
8
+1 Ааа, чувак, я знал этот ответ. Меня спросили об этом в интервью. Это очень хороший вопрос для понимания того, как try / catch / finally работает в стеке.
Но я не класс
10
Есть только один оператор печати, который может печатать число (последний:) print(e.getMessage()). Вы думали, что результат будет AB2C3: вы думали, что самый внешний catchблок будет выполнен дважды?
Адриан Пронк
В Java перед выполнением команды передачи управления из блока catch выполняется блок finally, если он существует. Если только код в блоке finally не передает управление извне, выполняется отложенная инструкция из блока catch.
Томас

Ответы:

198

Из спецификации языка Java 14.20.2. :

Если блок перехвата завершается преждевременно по причине R, то выполняется блок finally. Тогда есть выбор:

  • Если блок finally завершается нормально, то оператор try завершается преждевременно по причине R.

  • Если блок finally завершается преждевременно по причине S, тогда оператор try завершается преждевременно по причине S (и причина R отбрасывается) .

Итак, когда есть блок catch, который выдает исключение:

try {
    // ...
} catch (Exception e) {
    throw new Exception("2");
}

но есть также блок finally, который также генерирует исключение:

} finally {
    throw new Exception("3");
}

Exception("2")будут отброшены и только Exception("3")будут распространены.

Адам Семион
источник
72
Это даже верно для returnзаявлений. Если ваш блок finally имеет возврат, он переопределит любой возврат в блоке tryили catch. Из-за этих «особенностей» хорошей практикой является то, что блок finally никогда не должен выдавать исключение или иметь оператор return.
Августо
Это также наследуемое преимущество try-with-resources в Java 7. Оно сохраняет первоначальное исключение, если при закрытии ресурсов генерируется вторичное исключение, что обычно облегчает отладку.
w25r
19

Исключения, сгенерированные в блоке finally, подавляют исключение, сгенерированное ранее в блоке try или catch.

Пример Java 7: http://ideone.com/0YdeZo

Из примера Javadoc :


static String readFirstLineFromFileWithFinallyBlock(String path)
                                                     throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        if (br != null) br.close();
    }
}

Однако в этом примере, если методы readLine и закрывают оба исключения, метод readFirstLineFromFileWithFinallyBlock генерирует исключение, выброшенное из блока finally; исключение, выброшенное из блока try, подавляется.


Новый try-withсинтаксис Java 7 добавляет еще один шаг подавления исключений: исключения, сгенерированные в блоке try, подавляют те, что были сгенерированы ранее в части try-with.

из того же примера:

try (
        java.util.zip.ZipFile zf = new java.util.zip.ZipFile(zipFileName);
        java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
    ) {
        for (java.util.Enumeration entries = zf.entries(); entries.hasMoreElements();) {
            String newLine = System.getProperty("line.separator");
            String zipEntryName = ((java.util.zip.ZipEntry)entries.nextElement()).getName() + newLine;
            writer.write(zipEntryName, 0, zipEntryName.length());
        }
    }

Исключение может быть выдано из блока кода, связанного с оператором try-with-resources. В приведенном выше примере исключение может быть сгенерировано из блока try, и до двух исключений может быть сгенерировано из оператора try-with-resources, когда он пытается закрыть объекты ZipFile и BufferedWriter. Если исключение выдается из блока try, а одно или несколько исключений выбрасываются из оператора try-with-resources, то исключения, выбрасываемые из оператора try-with-resources, подавляются, и исключение, выбрасываемое блоком, является единственным это бросается методом writeToFileZipFileContents. Вы можете получить эти исключенные исключения, вызвав метод Throwable.getSuppressed из исключения, сгенерированного блоком try.


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

http://en.wikipedia.org/wiki/Error_hiding

SD
источник
9

Так throw new Exception("2");как выброшен из catchблока и нет try, он не будет пойман снова.
Смотри 14.20.2. Выполнение try-finally и try-catch-finally .

Вот что происходит:

try {
    try {
        System.out.print("A");         //Prints A
        throw new Exception("1");   
    } catch (Exception e) { 
        System.out.print("B");         //Caught from inner try, prints B
        throw new Exception("2");   
    } finally {
        System.out.print("C");         //Prints C (finally is always executed)
        throw new Exception("3");  
    }
} catch (Exception e) {
    System.out.print(e.getMessage());  //Prints 3 since see (very detailed) link
}
Марун
источник
да, верно, я вижу, что это происходит, но я искал объяснение - почему он так себя ведет
Kousalik
5

Ваш Вопрос очень очевиден, и ответ в той же степени прост. Объект Exception с сообщением как «2» перезаписывается объектом Exception с сообщением как «3».

Объяснение: Когда возникает исключение, его объект выбрасывается, чтобы перехватить блок для обработки. Но когда исключение возникает в самом блоке catch, его объект передается в блок OUTER CATCH (если есть) для обработки исключений. И то же самое произошло здесь. Объект исключения с сообщением «2» передается в блок захвата OUTER. Но подождите .. Прежде чем покинуть внутренний блок try-catch, он ДОЛЖЕН ИСПОЛЬЗОВАТЬСЯ ОКОНЧАТЕЛЬНО. Здесь произошли перемены, которые нас беспокоят. Новый объект EXCEPTION (с сообщением «3») выбрасывается или этот блок finally заменяет уже брошенный объект Exception (с сообщением «2»), в результате чего при выводе сообщения объекта Exception получается переопределенное значение, т. е. «3», а не «2».

Помните: только один объект исключения может быть обработан в блоке CATCH.

Бхарат
источник
2

finallyБлок всегда работает. Либо вы returnиз блока try, либо выдается исключение. Исключение, сгенерированное в finallyблоке, переопределит одно, сгенерированное в ветви catch.

Кроме того, выбрасывание исключения не приведет к самому выводу. Линия throw new Exception("2");ничего не напишет.

allprog
источник
1
да, я знаю, что выдача исключений ничего не дает сама по себе, но я не вижу причины, почему исключение 2 должно быть отброшено. Я снова немного умнее :-)
Kousalik
всегда очень долго и очень долго все может произойти (загадка wouter.coekaerts.be/2012/puzzle-dreams )
Dainius
0

Согласно вашему коду:

try {
    try {
        System.out.print("A");
        throw new Exception("1");   // 1
    } catch (Exception e) {
        System.out.print("B");      // 2
        throw new Exception("2");
    } finally {                     // 3
        System.out.print("C");      // 4 
        throw new Exception("3");
    }
} catch (Exception e) {             // 5
    System.out.print(e.getMessage());
}

Как вы можете видеть здесь:

  1. выведите A и выдает исключение # 1;
  2. это исключение поймано оператором catch и print B - # 2;
  3. блок, наконец, # 3выполняется после оператора try-catch (или только try, если не было ни одного исключения) и печатает C - # 4и выдает новое исключение;
  4. этот пойман внешним оператором catch # 5;

Результат есть ABC3. И 2опускается так же, как1

nazar_art
источник
Извините, Исключение ("1") не пропущено, но оно успешно перехвачено
Black Maggie
@ Black Maggie Кэшируется и выдается новое исключение => это не кешируется и программа завершается. И прежде чем этот блок окончательно исполняется.
nazar_art