W3docs

Java try...catch

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

try/catch — наименьшая единица обработки исключений в Java. Вы оборачиваете рискованный код в try, а затем добавляете один или несколько блоков catch, указывая, что делать при возникновении конкретного типа исключения. Вместе они образуют единую конструкцию: нельзя использовать одно без другого (или без finally, который мы рассмотрим в следующей главе).

Синтаксис

try {
  // code that might throw
} catch (ExceptionType e) {
  // code that runs only if a matching exception was thrown
}

Несколько вещей, которые легко упустить поначалу:

  • Переменная в catch (e по соглашению) ограничена областью видимости блока catch. Вы можете назвать её как угодно; e и ex — наиболее распространённые варианты.
  • Параметр catch фактически является финальным — вы можете его читать, но не следует переприсваивать. Обращение с ним как с иммутабельным делает код понятнее.
  • Блок try имеет собственную область видимости. Переменные, объявленные внутри него, не видны в catch или после конструкции. Если вам нужно значение, вычисленное в try, объявите переменную снаружи.
String content;
try {
  content = Files.readString(path);
} catch (IOException e) {
  content = "";   // works because `content` was declared outside the try
}
System.out.println(content);

Какие catch срабатывают

Блок catch (T e) выполняется, когда брошенное исключение является экземпляром T — включая любой подтип T. Таким образом:

  • catch (Exception e) перехватывает почти всё: IOException, NullPointerException, ваши собственные пользовательские исключения.
  • catch (RuntimeException e) перехватывает ошибки времени выполнения, но не проверяемые исключения вроде IOException.
  • catch (NullPointerException e) перехватывает только NPE (и подклассы, которых обычно нет).

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

try { ... }
catch (Exception e)        { ... }   // matches everything
catch (IOException e)      { ... }   // ERROR: unreachable

Всегда перечисляйте catch от наиболее специфичного к наиболее общему.

Что делать в catch

Блок catch — это не место, где исключения просто исчезают. Это место, где нужно решить, что делать при известном сбое. Реалистичные варианты:

  • Восстановление — повторная попытка, откат к значению по умолчанию, переключение на другой ресурс. Это наилучший сценарий, и он встречается реже, чем кажется.
  • Журналирование и повторное бросание — запишите детали и позвольте исключению распространиться дальше к обработчику верхнего уровня, который знает, что делать.
  • Оборачивание и повторное бросание — преобразуйте низкоуровневое исключение в то, что соответствует словарю данного слоя (IOExceptionConfigLoadException).
  • Преобразование в возвращаемое значение — когда сбой действительно ожидаем (например, разбор пользовательского ввода), верните Optional.empty() или сигнальное значение.

Чего не следует делать — перехватывать исключение и молча продолжать работу без журналирования или реакции. Именно так ошибки исчезают в никуда. Мы вернёмся к этому в главе о лучших практиках.

Чтение информации об исключении

Параметр catch — это объект. Три метода, которые вы будете использовать постоянно:

  • e.getMessage() — человекочитаемое описание. Может быть null.
  • e.toString() — имя класса плюс сообщение, например java.io.IOException: file not found.
  • e.printStackTrace() — выводит трассировку стека в System.err. Удобно при отладке, но в рабочем коде используйте настоящий логгер (он направляет трассировку через тот же канал, что и всё остальное).

Четвёртый метод, e.getCause(), возвращает исходное исключение, если одно из них было обёрнуто — полезно, когда нужно понять первопричину сбоя внутри слоя преобразования.

Перехват Exception и Throwable

Можно написать catch (Throwable t), и это перехватит всё — включая Error-ы вроде OutOfMemoryError. Не делайте этого. Error-ы означают, что JVM находится в затруднительном положении, и ваш код не в состоянии адекватно на них реагировать. Перехват Throwable маскирует ошибки, которые должны завершать процесс.

Используйте Exception или один из его подтипов. Если вам действительно нужно залогировать неожиданное исключение перед тем, как оно прервёт выполнение, правильная форма: catch (RuntimeException e) { log; throw; }.

Проработанный пример

Небольшая утилита, которая разбирает целое число из каждой строки входных данных. Некоторые строки содержат допустимые числа, некоторые — нет, одна равна null. Мы используем try/catch, чтобы продолжать работу после плохих строк и подсчитывать результаты. Вызов line.trim() бросает NullPointerException на записи null, а Integer.parseInt бросает NumberFormatException на всё, что не является целым числом — два отдельных типа исключений, каждый со своим catch.

java— editable, runs on the server

Вывод программы:

not an integer: "hello"
null line — skipped
not an integer: "3.14"
---
parsed: 3, failed: 3, sum: 118

Обратите внимание на два момента. Во-первых, программа завершается нормально. Без блоков catch первая плохая строка прервала бы выполнение, и всё последующее не было бы учтено. Во-вторых, два типа сбоев различаются: строка null попадает в ветку NullPointerException с соответствующим сообщением, а "hello" и "3.14" — в ветку NumberFormatException. Выбор catch по типу не даёт разным сбоям смешиваться.

Что дальше

Один catch — простейший случай. В реальном коде обычно нужно обрабатывать несколько разных сбоев одновременно. Перейдите к нескольким блокам catch и multi-catch в Java. После этого блок finally и try-with-resources завершают синтаксис, а throw и throws охватывают другую сторону: сигнализацию об ошибках из собственного кода.

Практика

Практика
Блок try выполняет `a[5] = 1;` на массиве из 2 элементов. Первым написан `catch (RuntimeException e)`, вторым — `catch (ArrayIndexOutOfBoundsException e)`. Что произойдёт?
Блок try выполняет `a[5] = 1;` на массиве из 2 элементов. Первым написан `catch (RuntimeException e)`, вторым — `catch (ArrayIndexOutOfBoundsException e)`. Что произойдёт?
Was this page helpful?