Несколько блоков 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 используется форма мультиперехвата для группировки двух сбоев с одинаковой реакцией.
Каждый входной элемент проходит свой путь через блоки catch, но каждая итерация выводит одну чистую строку — программа не бросает исключение в лицо пользователю.
Что дальше
try/catch обрабатывает штатный путь и путь с ошибкой. Третья конструкция, finally, занимается тем, что должно произойти в любом случае. Продолжайте читать о блоке finally в Java.