Menu

Java try-catch: обработка исключений без аварийного завершения

Как использовать try-catch в Java для обработки исключений: перехват конкретных типов, блок finally, try-with-resources и ошибки, которые скрывают баги.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

Перехват исключения вместо аварийного завершения

Вы уже знаете, что, когда что-то идёт не так, Java выбрасывает исключение, а необработанное исключение останавливает программу со стек-трейсом. Блок try-catch - это способ взять управление в свои руки: вы оборачиваете рискованный код в try, и если он выбрасывает исключение, Java переходит к подходящему блоку catch вместо аварийного завершения.

В тот момент, когда 10 / 0 выбрасывает исключение, остальная часть блока try пропускается, и управление переходит к catch. После завершения catch выполнение продолжается как обычно - программа не умирает.

Переменная catch содержит исключение

e в catch (ArithmeticException e) - это реальный объект. Он несёт информацию о том, что пошло не так, и самое полезное - это сообщение:

e.getMessage() возвращает краткое описание. При отладке e.printStackTrace() выводит полный трейс, показывающий, откуда именно пришло исключение - обращайтесь к нему, когда одного сообщения недостаточно, чтобы найти причину.

Перехватывайте конкретный тип, а не всё подряд

Блок catch перехватывает только исключения, соответствующие объявленному типу (или его подклассу). Самая большая ошибка новичков - перехватывать Exception, чтобы заставить проблему «исчезнуть»:

// Не делайте так - это скрывает реальные баги
try {
    doWork();
} catch (Exception e) {
    // поглощает NullPointerException, опечатки, логические ошибки... всё подряд
}

Перехватывайте самый узкий тип, который вы действительно можете обработать. Если вы ожидаете некорректный числовой ввод, перехватывайте NumberFormatException. Всему, что вы не предусмотрели, нужно позволить распространяться, чтобы вы действительно об этом узнали, а не продолжали молча работать в сломанном состоянии.

Обработка нескольких типов исключений

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

Когда два типа обрабатываются одинаково, используйте один multi-catch с |, вместо того чтобы дублировать блок:

Подводный камень: более общий тип должен идти после конкретных. Если поставить catch (Exception e) первым, последующие, более конкретные блоки станут недостижимыми, и компилятор это отклонит.

finally выполняется всегда

Блок finally выполняется после try и любого catch независимо от того, что произошло - успех, перехваченное исключение или даже ранний return. Здесь место для очистки, которая должна происходить всегда.

«Closing resource» выводится независимо от того, возникнет исключение или нет. Однако избегайте return изнутри finally - это может незаметно отбросить исключение или перезаписать значение, возвращённое из блока try.

try-with-resources закрывает всё за вас

Когда вы работаете с чем-то, что нужно закрывать - файлом, сетевым соединением, выражением базы данных, - объявление этого внутри try (...) закрывает его автоматически по завершении блока, даже если выброшено исключение. Подходит любой тип, реализующий AutoCloseable.

Это заменяет старый шаблон с открытием в try и закрытием в finally, и менее подвержено ошибкам, потому что вы не можете забыть закрыть. Предпочитайте его всякий раз, когда имеете дело с закрываемым ресурсом.

Не используйте исключения для обычного потока управления

try-catch предназначен для исключительных ситуаций, а не для обычных условий. Перехват исключения дороже, чем простая проверка, и делает код труднее для чтения. Если вы можете проверить условие заранее, сделайте именно так:

// Избегайте: использовать catch для проверки отсутствующего ключа
try {
    process(map.get(key).trim());
} catch (NullPointerException e) {
    // обработать отсутствующий ключ
}

// Предпочитайте: проверять явно
String value = map.get(key);
if (value != null) {
    process(value.trim());
}

Оставьте catch для вещей, действительно находящихся вне вашего контроля: некорректный ввод пользователя, отсутствующие файлы, сбои сети.

Далее: NullPointerException

Самое распространённое исключение, которое вы будете перехватывать (и вызывать) в Java, - это NullPointerException: оно возникает в тот момент, когда вы вызываете метод у чего-то, что оказалось null. Далее мы подробно разберём, что именно его вызывает, как читать его стек-трейс и какие привычки изначально не дают ему возникать.

Часто задаваемые вопросы

Как использовать try-catch в Java?

Поместите код, который может выбросить исключение, в блок try, а затем добавьте блок catch, указав тип исключения, которое хотите обработать: try { risky(); } catch (IOException e) { ... }. Если код в try выбрасывает подходящее исключение, Java сразу переходит к блоку catch вместо аварийного завершения. Переменная (e) содержит объект исключения, поэтому вы можете прочитать его сообщение через e.getMessage().

Для чего нужен блок finally в Java?

Блок finally выполняется после try/catch в любом случае - независимо от того, отработал ли код успешно, выбросил исключение или даже выполнил ранний return. Это место для очистки, которая должна происходить всегда, например закрытие файла или освобождение блокировки. Для закрытия ресурсов обычно чище использовать try-with-resources, потому что он закрывает их автоматически.

Стоит ли перехватывать Exception или конкретный тип исключения?

Перехватывайте самый конкретный тип, который вы действительно можете обработать. Перехват Exception (или ещё хуже - Throwable) поглощает любую проблему, включая баги вроде NullPointerException, и скрывает настоящую причину. Перехватывайте NumberFormatException, если ожидаете именно это, и позволяйте непредвиденным исключениям распространяться, чтобы вы о них узнали.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ