Исключение, брошенное в блок захвата - будет ли оно снова поймано?

180

Это может показаться вопросом программирования, и я подумал, что знаю ответ, но теперь мне нужно перепроверить. В этом фрагменте кода ниже будет ли исключение, генерируемое в первом блоке catch, перехватываться общим блоком catch Exception ниже?

try {
  // Do something
} catch(IOException e) {
  throw new ApplicationException("Problem connecting to server");
} catch(Exception e) {
  // Will the ApplicationException be caught here?
}

Я всегда думал, что ответ будет отрицательным, но теперь у меня есть странное поведение, которое может быть вызвано этим. Ответ, вероятно, одинаков для большинства языков, но я работаю на Java.

roryf
источник
2
Возможно, вы могли бы описать «странное поведение»?
Джеффри Л Уитледж
Вы уверены, что ApplicationException не генерируется в другом месте и распространяется до этого блока?
Сблунди
Я заметил это в моей Eclipse IDE. Это заставляет меня помещать «бросить новое исключение» в блок try, но я не знаю почему. Я делал это в прошлом, не делая этого. Я не понимаю, почему потребуется блок try. Множество примеров в Google показывают людей, которым не нужен блок try. Это потому, что я бросаю внутрь оператора if?
Джангофан

Ответы:

214

Нет, поскольку новый throwне находится в tryблоке напрямую.

Крис Шут-Янг
источник
Скажем, если код подобен этому try {} catch (Exception e) {System.err.println («In catch Exception:» + e.getClass ()); } catch (IOException e) {System.err.println ("В catch IOException:" + e.getClass ()); } и код в блоке try генерирует IO Exception, перейдет ли он в непосредственный общий блок Exception или переместится в блок catch IOException?
sofs1
4
@ user3705478 Код не будет компилироваться, чтобы избежать такой ошибочной ситуации. В общем, вам не разрешено иметь перехват для подкласса после перехвата его суперкласса в том же блоке try.
Крис Шутер-Янг
Спасибо. Так что я получу ошибку во время компиляции, верно? Я проверю это, когда вернусь домой.
sofs1
Это зависит от @ user3705478, если RuntimeExceptionиз catchблока выдается a, ошибки компиляции не будет.
Рэнди Дев
@AndrewDunn Я не думаю, что это вопрос пользователя user3705478, а скорее то, что происходит, если родительское исключение catchуказано в списке перед дочерним catchпредложением исключения . Насколько я понимаю, Java запрещает это, и это ловится во время компиляции.
Крис Шестер-Янг
71

Нет, это очень легко проверить.

public class Catch {
    public static void main(String[] args) {
        try {
            throw new java.io.IOException();
        } catch (java.io.IOException exc) {
            System.err.println("In catch IOException: "+exc.getClass());
            throw new RuntimeException();
        } catch (Exception exc) {
            System.err.println("In catch Exception: "+exc.getClass());
        } finally {
            System.err.println("In finally");
        }
    }
}

Следует напечатать:

В улове IOException: класс java.io.IOException
В конце концов
Исключение в потоке "main" java.lang.RuntimeException
        в Catch.main (Catch.java:8)

Технически это могло быть ошибкой компилятора, зависящей от реализации, неопределенным поведением или чем-то еще. Тем не менее, JLS довольно хорошо прибит, и компиляторы достаточно хороши для такого рода простых вещей (случай с обобщенным углом может отличаться).

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

Обратите внимание, что блок finally всегда выполняется, даже если выполняется блок catch (кроме глупых случаев, таких как бесконечные циклы, присоединение через интерфейс инструментов и уничтожение потока, перезапись байт-кода и т. Д.).

Том Хотин - Tackline
источник
3
Самый очевидный способ избежать - finallyэто, конечно, позвонить System.exit. :-P
Крис Шестер-Янг
3
@Chris Jester-Young for(;;);короче, содержится в языке, мало что дает в плане побочных эффектов и, для меня, более очевидно.
Том Хотин - Tackline
1
System.exitболее дружественный к процессору! : -O Но да, ладно, это явно субъективный критерий. Кроме того, я не знал, что ты игрок в гольф. ;-)
Крис Шестер-Янг
28

Спецификация языка Java говорит в разделе 14.19.1:

Если выполнение блока try завершается внезапно из-за выброса значения V, существует выбор:

  • Если во время выполнения тип V назначается параметру любого предложения catch оператора try, то выбирается первое (самое левое) такое предложение catch. Значение V присваивается параметру выбранного предложения catch, и выполняется блок этого предложения catch. Если этот блок завершается нормально, то оператор try завершается нормально; если этот блок завершается преждевременно по какой-либо причине, то оператор try завершается преждевременно по той же причине.

Ссылка: http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#24134

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

Одна связанная и запутанная вещь, которую нужно знать, это то, что в структуре try-[catch] -finally блок finally может выдать исключение, и если это так, любое исключение, выброшенное блоком try или catch, будет потеряно. Это может сбить с толку, когда вы впервые увидите это.

Алекс Миллер
источник
Просто хотел добавить, что начиная с Java 7 вы можете избежать этого, используя try-with-resources. Затем, если tryAND finallyоба throw, finallyподавляется, но также ДОБАВЛЯЕТСЯ в исключение из try. Если catchбросает также, вам не повезло, если вы сами не справитесь с этим с помощью addSuppressed и добавите tryисключение - тогда у вас есть все три.
LAFK говорит восстановить Монику
6

Если вы хотите выбросить исключение из блока catch, вы должны сообщить свой метод / класс / и т. Д. что для этого нужно выбросить указанное исключение. Вот так:

public void doStuff() throws MyException {
    try {
        //Stuff
    } catch(StuffException e) {
        throw new MyException();
    }
}

И теперь ваш компилятор не будет на вас кричать :)

Mastergeek
источник
4

Нет - как сказал Крис Джестер-Янг, это будет выброшено на следующую попытку в иерархии.

Ян П
источник
2

Как сказано выше ...
Я хотел бы добавить, что если у вас возникли проблемы с просмотром происходящего, если вы не можете воспроизвести проблему в отладчике, вы можете добавить трассировку перед повторным вызовом нового исключения (со старой доброй системой .out.println в худшем случае, с хорошей системой журналов, как log4j в противном случае).

PhiLho
источник
2

Это не будет поймано вторым блоком захвата. Каждое исключение отлавливается только внутри блока try. Вы можете вложить попытки хотя (не то, что это хорошая идея вообще):

try {
    doSomething();
} catch (IOException) {
   try {
       doSomething();
   } catch (IOException e) {
       throw new ApplicationException("Failed twice at doSomething" +
       e.toString());
   }          
} catch (Exception e) {
}
Винко Врсалович
источник
Я написал похожий код. Но я не убежден. Я хочу вызвать Thread.sleep () в моем блоке catch. Но Thread.sleep создает исключение InterruptedException. Правильно ли (лучше всего) делать это так, как вы показали в своем примере?
riroo
1

Нет, поскольку все перехваты ссылаются на один и тот же блок try, поэтому выброс изнутри блока перехвата будет перехвачен включающим блоком try (возможно, в методе, который вызвал этот)

Uri
источник
-4

Старая запись, но переменная "e" должна быть уникальной:

try {
  // Do something
} catch(IOException ioE) {
  throw new ApplicationException("Problem connecting to server");
} catch(Exception e) {
  // Will the ApplicationException be caught here?
}
Тед К
источник
5
Это должен быть комментарий, а не ответ. Это ничего не дает для решения актуального вопроса - это всего лишь критика вопроса ОП.
Дерек W