Виняток Java не спійманий?


170

У мене невелика теоретична проблема із спробними конструкціями.

Я вчора склав практичний іспит щодо 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")?


8
+1 А-а-а, я знав цю відповідь. Про це мене запитали в інтерв'ю. Це дуже гарне питання для розуміння того, як спробувати / зловити / нарешті працює на стеці.
Але я не клас Wrapper

10
Є лише одне твердження про друк, яке може надрукувати номер (останнє print(e.getMessage()):). Ви думали, що результат буде таким AB2C3: ви думали, що зовнішній catchблок буде виконаний двічі?
Адріан Пронк

У Java, перед тим, як виконати інструкцію про те, що контроль передачі з блоку лову виконується, блок остаточно виконується за умови його наявності. Якщо тільки код, що знаходиться в остаточному блоці, не передає керування назовні, виконується відкладена інструкція з блоку лову.
Томас

Відповіді:


198

З специфікації мови Java 14.20.2. :

Якщо блок лову різко завершується з причини R, тоді остаточно блок виконується. Тоді є вибір:

  • Якщо блок нарешті завершується нормально, то оператор try різко завершується з причини R.

  • Якщо остаточний блок різко завершується з причини S, то оператор спробу завершується різко з причини S (а причина R відкидається) .

Отже, коли є блок ловлі, який видає виняток:

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

але є також нарешті блок, який також кидає виняток:

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

Exception("2")буде відкинуто і тільки Exception("3")буде розповсюджено.


72
Це навіть справедливо для returnтверджень. Якщо ваш остаточний блок має повернення, він замінить будь-яке повернення в a tryабо catchблок. Через ці "особливості" хороша практика полягає в тому, що, нарешті, блок ніколи не повинен викидати виняток і не мати заяви про повернення.
Август

Це також є перевагою успадкованого випробування з ресурсами в Java 7. Він зберігає початковий виняток, якщо вторинне виключення створюється при закритті ресурсів, як правило, полегшує налагодження.
w25r

19

Винятки, кинуті в остаточному блоці, придушують виняток, кинутий раніше у блоці спробу або лову.

Приклад 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 викидає виняток, викинутий з остаточно блоку; виняток, викинутий з блоку спробу, придушується.


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

з того ж прикладу:

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-resource. У наведеному вище прикладі виключення може бути викинуто з блоку спробу, і до двох винятків може бути викинуто з оператора спробу використання ресурсів, коли він намагається закрити об'єкти ZipFile та BufferedWriter. Якщо виняток викидається з блоку спробу і один або більше винятків викидаються з оператора try-with-ресурси, то винятки, викинуті з оператора try-with-ресурси, придушуються, а виняток, кинутий блоком, є одним що викидається методом writeToFileZipFileContents. Ви можете отримати ці придушені винятки, зателефонувавши методу Throwable.getSuppression з винятку, викинутого блоком спробу.


У коді з питання кожен блок очевидно відкидає старий виняток, навіть не записуючи його, що не добре, коли ви намагаєтеся вирішити деякі помилки:

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


9

Оскільки throw new Exception("2");викинутий з catchблоку, а ні try, він знову не буде спійманий.
Див. 14.20.2. Виконання спробу-нарешті та спробу-лов-нарешті .

Ось що відбувається:

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

Ваше запитання дуже очевидно, і відповідь проста в тій же мірі. Об'єкт винятку з повідомленням "2" перезаписується об'єктом винятку з повідомленням як "3".

Пояснення: Коли трапляється виняток, його об'єкт він кидає, щоб схопити блок для обробки. Але коли в самому блоці лову виникають винятки, його об'єкт передається до блоку OUTER CATCH (якщо такий є) для обробки винятків. І те саме сталося тут. Об'єкт винятку з повідомленням "2" переноситься в БЛОК вибору OUTER. Але зачекайте . Перш ніж вийти з внутрішнього блоку пробного лову, МОЖЕ ВИКОНАТИ ВЗАЄМО. Тут відбулися зміни, які нас хвилюють. Викидається новий об’єкт EXCEPTION (з повідомленням "3") або цей остаточний блок, який замінив уже викинутий об'єкт Exception (з повідомленням "2"). В результаті чого, коли друкується повідомлення об'єкта Exception, ми отримуємо перевизначене значення, тобто "3", а не "2".

Пам'ятайте: лише один об'єкт виключення може оброблятись на блоці CATCH.


2

finallyБлок завжди працює. Або ви returnзсередини пробного блоку, або виняток. Виняток, кинутий у finallyблок, буде заміняти той, кинутий у гілку улову.

Крім того, кидання винятку не спричинить жодного результату. Рядок throw new Exception("2");нічого не випише.


1
так, я знаю, що викидати програму Exception нічого не потрібно, але я не бачив причини, чому виняток 2 слід скинути. Я знову трохи розумніший :-)
Kousalik

завжди дуже довгий час і в дуже довгий час все може трапитися (перевірка головоломки wouter.coekaerts.be/2012/puzzle-dreams )
Дайніус

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. цей виняток спричинив заяву про вилов та друк B - # 2;
  3. блок, нарешті, # 3виконується після try-catch (або лише спробувати, якщо не сталося жодного винятку) і друкує C - # 4та викидає новий виняток;
  4. цей підхопив зовнішню заяву про вилов # 5;

Результат є ABC3. І 2опускається так само, як і1


Вибачте, виняток ("1") не опущено, але успішно підхоплено
Black Maggie

@Black Maggie Це вимкнено кешування та викинуто нове виняток => це не кешовано, а програма не припиняється. І перш ніж цей блок нарешті буде виконаний.
nazar_art
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.