W3docs

Несколько блоков catch и мультиперехват в Java

Обработка разных типов исключений в Java: несколько блоков catch или мультиперехват (Type1 | Type2 e) в одном блоке.

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

Несколько последовательных блоков catch

try {
  String body = httpClient.get(url);
  Config cfg  = parser.parse(body);
  apply(cfg);
} catch (IOException e) {
  retryLater(url);
} catch (ParseException e) {
  report("config file is malformed: " + e.getMessage());
} catch (SecurityException e) {
  report("not allowed to apply config");
}

При каждом броске выполняется только один блок catch — первый, тип которого является экземпляром брошенного исключения. После его выполнения управление передаётся инструкции, следующей после всей конструкции try, а не к следующему catch.

Порядок важен: от частного к общему

Блок catch (T e) перехватывает всё, что является T или любым подклассом T. Если суперкласс указан раньше подкласса, блок catch для подкласса становится недостижимым, и компилятор откажет в этом:

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

Правильный порядок — самый узкий тип сверху, самый широкий снизу:

try { ... }
catch (FileNotFoundException e) { ... }   // most specific
catch (IOException e)           { ... }   // wider
catch (Exception e)             { ... }   // catch-all (used sparingly)

Это также полезное упражнение для проектирования: написание блоков catch заставляет задуматься о том, какие сбои блок вообще может породить.

Мультиперехват (один блок, несколько типов)

В Java 7 добавлена форма мультиперехвата: один блок обрабатывает несколько несвязанных типов исключений, разделённых символом |:

try {
  return parser.read(file);
} catch (IOException | ParseException e) {
  log.warn("could not load config: " + e);
  return Config.defaults();
}

Используйте эту форму, когда обработка одинакова для нескольких различных сбоев. Это короче двух почти идентичных блоков catch и сразу показывает, что «эти типы имеют общую реакцию».

Важные правила:

  • Типы в объединении не должны быть связаны наследованием. IOException | FileNotFoundException не скомпилируется — один является подтипом другого, поэтому более широкий уже включает его.
  • Внутри блока переменная e имеет тип общего супертипа перечисленных типов. Можно вызывать методы, объявленные для этого супертипа, но не методы, специфичные для подтипов. Для большинства задач (логирование, оборачивание) достаточно getMessage() и toString().
  • Параметр catch в мультиперехвате является неявно финальным — его нельзя переназначить. (Одиночные catch лишь эффективно финальны; на практике разница несущественна.)

Когда разбивать блок try

Распространённая ошибка с точки зрения читаемости — обернуть весь метод в один огромный блок try, а затем перехватывать всё, что может бросить любая строка. Логика обработки внизу оказывается запутанной — непонятно, какая именно строка завершилась ошибкой.

Два более чистых подхода в такой ситуации:

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

Чем меньше блок try, тем проще рассуждать о блоках catch. На вопрос «Какая строка может бросить это?» всегда должен быть короткий ответ.

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

Небольшая программа, выполняющая три действия — разбор числа, поиск в массиве, деление — каждое из которых может завершиться по-своему. Каждый сбой перехватывается отдельным обработчиком, чтобы сообщения оставались конкретными. В последнем блоке catch используется форма мультиперехвата для группировки двух сбоев с одинаковой реакцией.

java— editable, runs on the server

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

Что дальше

try/catch обрабатывает штатный путь и путь с ошибкой. Третья конструкция, finally, занимается тем, что должно произойти в любом случае. Продолжайте читать о блоке finally в Java.

Практика

Практика
Скомпилируется ли этот код?\n\n```java\ntry { ... }\ncatch (IOException | FileNotFoundException e) {\n log(e);\n}\n```
Скомпилируется ли этот код?\n\n```java\ntry { ... }\ncatch (IOException | FileNotFoundException e) {\n log(e);\n}\n```
Was this page helpful?