Зачем ловить исключения в Java, если вы можете ловить Throwables?

Недавно у нас возникла проблема с серверным приложением Java, в котором приложение выдавало ошибки, которые не были обнаружены, потому что Error является отдельным подклассом Throwable, и мы перехватывали только исключения.

Мы решили непосредственную проблему, перехватывая Throwables, а не Exceptions, но это заставило меня задуматься о том, почему вам когда-либо захочется перехватывать исключения, а не Throwables, потому что в этом случае вы пропустите ошибки.

Итак, зачем вам ловить исключения, если вы можете ловить Throwables?

Ответов (14)

Решение

Все немного зависит от того, что вы собираетесь делать с ошибкой, как только вы ее поймали. В общем, перехват ошибок, вероятно, не следует рассматривать как часть вашего «обычного» потока исключений. Если вы поймаете одну из них, вам не следует думать о том, чтобы «вести себя так, как будто ничего не произошло», потому что JVM (и различные библиотеки) будут использовать ошибки как способ сигнализировать о том, что «произошло что-то действительно серьезное, и нам нужно выключите как можно скорее ". В общем, лучше слушать их, когда они говорят вам, что конец близок.

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

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

  • Бывают случаи, когда вы действительно хотите остановить нормальный ход потока: например, если вы находитесь в сервлете, вы можете не захотеть, чтобы обработчик исключений по умолчанию выполняемого сервлетом объявлял миру, что у вас есть OutOfMemoryError, независимо от того, есть ли у вас OutOfMemoryError или ты не сможешь оправиться от этого.
  • Иногда возникает ошибка, если JVM может полностью устранить причину ошибки. Например, если ошибка OutOfMemoryError возникает при попытке выделить массив, по крайней мере, в Hotspot, кажется, что вы можете безопасно исправить это. (Конечно, есть и другие случаи, когда может возникнуть OutOfMemoryError, когда небезопасно пытаться продолжить работу.)

Итак, суть в следующем: если вы поймаете Throwable / Error, а не Exception, это должен быть четко определенный случай, когда вы знаете, что «делаете что-то особенное» .

Изменить: возможно, это очевидно, но я забыл сказать, что на практике JVM может фактически не вызывать ваше предложение catch при ошибке. Я определенно видел, как Hotspot бойко замалчивает попытки поймать определенные OutOfMemoryErrors и NoClassDefFoundError.

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

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

However, I believe there are cases when it would be appropriate to catch an Error and not report it. I'm referring to UnsatisfiedLinkError. In JAI the library uses some native libraries to implement most of the operators for performance reasons, however if the library fails to load (doesnt exist, wrong format, unsupported platform) the library will still function because it will fall back into a java only mode.

Почему бы не поймать их всех? Затем зарегистрируйте их, по крайней мере, вы знаете, что у вас есть ошибка. Так что лучше поймать Throwable / s, чем Exception / s.

Ошибку ловить нет смысла .

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

Например, одна распространенная ошибка:

java.lang.OutOfMemoryError

There is NOTHING you can do when that happens. Is already too late, the JVM has exhausted all its options to get more memory but it is impossible.

See this other answer to understand more about the three kinds of exceptions.

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

try {
    ...
} catch(Exception e) {
    ...
}

Это уловах все ошибки , которые могут быть возмещена и все те , которые указывают на ошибку в коде, например InvalidArgumentException, NullPointerException . Затем вы можете автоматически отправить электронное письмо, отобразить окно сообщения или что угодно, поскольку сама JavaVM все еще работает нормально.

Все производное Error - это что-то очень плохое, ничего не поделаешь. Вопрос в том, есть ли смысл ловить a OutOfMemoryError или a VirtualMachineError . (Это ошибка самой JavaVM, возможно, вы даже не можете отобразить окно сообщения или отправить электронное письмо)

Вероятно Error, вам не следует быть производным от класса , вы должны быть производным от Exception или RuntimeException .

Немного не по теме, но вы также можете прочитать эту очень хорошую статью об исключениях.

Из документации Java API:

Класс Exceptionи его подклассы представляют собой форму Throwable, указывающую на условия, которые разумное приложение может захотеть уловить.

Это Errorподкласс, Throwableкоторый указывает на серьезные проблемы, которые разумное приложение не должно пытаться уловить.

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

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

Чрезмерно агрессивный перехват java.lang.Exception может привести к серьезным ошибкам в приложениях, потому что неожиданные исключения никогда не всплывают, никогда не обнаруживаются во время разработки / тестирования и т. Д.

Лучшая практика: только улов

  1. Исключения, с которыми вы можете справиться
  2. Исключения, которые необходимо отловить

НЕ когда - либо поймать Throwable или Error и вы должны , как правило , не просто поймать общий Exception либо. Error s - это вещи, от которых большинство разумных программ не могут избавиться. Если вы знаете, что происходит, вы можете исправить одну конкретную ошибку, но в этом случае вы должны перехватить только эту конкретную ошибку, а не все ошибки в целом.

Хорошая причина не ловить Error - из-за ThreadDeath. ThreadDeath - вполне нормальное явление, которое теоретически может быть выброшено откуда угодно (его могут генерировать другие процессы, такие как сама JVM), и весь его смысл в том, чтобы убить ваш поток. ThreadDeath явно является, Error а не, Exception потому что слишком многие люди ловят все Exception s. Если вам когда-либо приходилось ловить ThreadDeath, вы должны повторно выбросить его, чтобы ваша нить действительно умерла.

Если у вас есть контроль над источником, его, вероятно, следует реструктурировать так, чтобы он создавал Exception вместо файла Error . Если вы этого не сделаете, вам, вероятно, следует позвонить поставщику и пожаловаться. Error s следует зарезервировать только для оконечных устройств без возможности восстановления после них.

Этот пост не сделает счастливыми людей, считающих, что "отмеченные исключения - плохие". Однако я основываю свой ответ на том, как должны использоваться исключения Java, как это определено людьми, создавшими язык.

Краткая справочная таблица:

  • Метательный - никогда не лови это
  • Ошибка - указывает на ошибку виртуальной машины - никогда не улавливайте это
  • RuntimeException - указывает на ошибку программиста - никогда не ловите это
  • Исключение - никогда не лови это

Причина, по которой вы не должны перехватывать Exception, заключается в том, что оно перехватывает все подклассы, включая RuntimeException.

Причина, по которой вы не должны ловить Throwable, заключается в том, что он перехватывает все подклассы, включая Error и Exception.

Есть исключения (без каламбура) из приведенных выше «правил»:

  • Код, с которым вы работаете (от третьего лица), выдает Throwable или Exception
  • Вы запускаете ненадежный код, который может привести к сбою вашей программы, если возникнет исключение.

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

Я пойду немного другим путем, чем другие.

Есть много случаев, когда вы хотели бы поймать Throwable (в основном, чтобы регистрировать / сообщать о том, что произошло что-то плохое).

Однако вам нужно быть осторожным и отбросить все, с чем вы не можете справиться.

Особенно это касается ThreadDeath.

Если вы когда-нибудь поймаете Throwable, обязательно сделайте следующее:

try {
    ...
} catch (SomeExceptionYouCanDoSomethingWith e) {
    // handle it
} catch (ThreadDeath t) {
    throw t;
} catch (Throwable t) {
    // log & rethrow
}

Есть по крайней мере один случай, когда я думаю, что вам, возможно, придется перехватить генерируемое или универсальное исключение - если вы запускаете отдельный поток для выполнения задачи, вы можете узнать, уловил ли метод «run» потока некоторые исключение или нет. В этом случае вы, вероятно, сделаете что-то вроде этого:


public void run() {
   try {
       ...
   }
   catch(Throwable t) {
       threadCompletionError = t;
   }
}

Я действительно не уверен, что это лучший подход, но он работает. И у меня была ошибка "ClassNotFound", вызванная JVM, и это ошибка, а не исключение. Если я позволю создать исключение, я не уверен, как его перехватить в вызывающем потоке (возможно, есть метод, но я о нем пока не знаю).

Что касается метода ThreadDeath, не вызывайте метод «Thread.stop ()». Вызовите Thread.interrupt и попросите ваш поток проверить, не был ли он кем-то прерван.

A lot of the other answers are looking at things too narrowly.

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

Однако, если вы пишете «системный код», например фреймворк или другой низкоуровневый код, тогда вам вполне может понадобиться поймать Throwable. Причина в том, чтобы попытаться сообщить об исключении, возможно, в файле журнала. В некоторых случаях регистрация не удастся, но в большинстве случаев она будет успешной, и у вас будет информация, необходимая для решения проблемы. После того, как вы сделали попытку регистрации, вы должны либо повторно запустить, либо убить текущий поток, либо выйти из всей JVM.