Объясните, почему обработчик общего типа исключения нельзя ставить перед обработчиком его подкласса.
Обработчики catch проверяются сверху вниз, и выбирается первый подходящий обработчик. Обработчик общего типа уже перехватывает исключения всех его подклассов, поэтому расположенный после него обработчик подкласса становится недостижимым и вызывает ошибку компиляции.
Иерархия исключений в Java основана на наследовании: например, FileNotFoundException является подклассом IOException, а IOException — подклассом Exception. Такой подход позволяет обрабатывать ошибки на подходящем уровне обобщения: специфические проблемы — отдельно, остальные ошибки категории — общим обработчиком.
Последовательная проверка catch решает исходную задачу выбора одного обработчика без неоднозначности. Разработчик явно задаёт приоритет: сначала более узкие случаи, затем более общий резервный сценарий.
Если поставить catch (IOException) перед catch (FileNotFoundException), любое FileNotFoundException будет подходить первому обработчику. До второго обработчика выполнение никогда не дойдёт.
Это не просто потенциальная логическая ошибка во время выполнения. Компилятор обнаруживает недостижимый обработчик и отклоняет программу, поскольку порядок обработчиков делает ветвь заведомо бесполезной.
Тип исключения проверяется на совместимость с типом параметра catch. Если фактический объект исключения является экземпляром подкласса, он также совместим с обработчиком его суперкласса.
Поэтому сначала располагают наиболее специфичные обработчики, а затем более общие:
В этом примере FileNotFoundException попадает в первый обработчик, а другие checked-исключения типа IOException — во второй. Если поменять обработчики местами, компилятор сообщит, что обработчик FileNotFoundException недостижим.
Порядок важен только для пересекающихся типов, когда один тип является подтипом другого. Для независимых типов, например IOException и SQLException, оба обработчика допустимы: каждый перехватывает свою ветвь иерархии.
Слишком общий обработчик, например catch (Exception), может скрыть полезную диагностику и привести к неправильной реакции приложения. Его обычно используют после специализированных обработчиков и только там, где действительно предусмотрена единая политика обработки.
Сервис читает файл конфигурации. Отсутствующий файл означает, что нужно применить значения по умолчанию, ошибка чтения существующего файла требует сообщения оператору, а непредвиденная ошибка должна попасть в общий журнал.
Можно использовать один catch (Exception). Плюс такого решения — минимум кода, но теряется различие между штатным отсутствием файла и настоящей ошибкой. Другой вариант — несколько обработчиков, расположенных без учёта иерархии; такой вариант не скомпилируется, если общий тип окажется раньше частного.
Правильное решение — поставить обработчик FileNotFoundException первым, затем IOException, а общий Exception использовать последним только при необходимости. Это сохраняет специфичную бизнес-логику, обеспечивает резервную обработку и делает намерение разработчика явным.
Вопрос: Меняется ли правило порядка обработчиков для непроверяемых исключений, например RuntimeException?
Ответ: Нет. Проверка достижимости обработчиков основана на иерархии типов, а не на том, является ли исключение checked или unchecked. catch (RuntimeException) перед catch (NullPointerException) также делает второй обработчик недостижимым и приводит к ошибке компиляции.
Вопрос: Что произойдёт, если обработчики предназначены для двух классов-сиблингов, не находящихся в отношении наследования?
Ответ: Оба обработчика допустимы. Например, IOException и SQLException имеют разные ветви иерархии, поэтому один не перехватывает экземпляры другого. Порядок между ними не влияет на выбор обработчика для конкретного исключения, хотя единый стиль — располагать более специфичные варианты выше общих в пределах каждой ветви.
Вопрос: Почему нельзя указать в multi-catch тип и его подкласс?
Ответ: Альтернативы multi-catch должны быть взаимоисключающими с точки зрения иерархии типов. Конструкция с IOException | FileNotFoundException избыточна: любое FileNotFoundException уже покрывается IOException. Поэтому компилятор запрещает такие альтернативы, требуя либо оставить только общий тип, либо использовать отдельные последовательные catch, если для подкласса нужна особая логика.