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