Вам дан блок try, в котором нет явного выброса или вызова метода с объявленным проверяемым исключением. Почему компилятор может признать catch для конкретного проверяемого типа недостижимым?
Компилятор считает catch для проверяемого исключения недостижимым, если анализируемое тело try не может передать это исключение или его подкласс по объявленному контракту. Такой catch вызывает ошибку компиляции, потому что Java проверяет достижимость обработчиков статически.
Для непроверяемых исключений — RuntimeException и его подклассов, а также Error — это ограничение не применяется: они могут возникнуть практически в любой точке выполнения.
Проверяемые исключения появились как средство сделать ошибки частью контракта метода. Вызывающий код должен явно выбрать стратегию: обработать исключение или передать его дальше через throws.
Проверка достижимости catch дополняет этот механизм. Она не позволяет объявлять обработчик для проверяемой ошибки там, где согласно статической информации такая ошибка возникнуть не может.
Java не анализирует все возможные внутренние обстоятельства выполнения. Компилятор опирается на конструкцию программы и объявления методов: наличие throw, сигнатуры throws, конструкторов и других операций, способных передать проверяемое исключение.
Если после рефакторинга вызов, объявлявший IOException, заменён на вызов без такого исключения, старый catch (IOException) может стать недостижимым. Попытка сохранить его «на всякий случай» приведёт не к предупреждению, а к ошибке компиляции.
Для проверяемого исключения обработчик достижим, если тело try содержит операцию, которая статически может выбросить этот тип или его подкласс. Источником такой информации обычно служат явный throw и вызов метода с соответствующим throws.
Например, если метод объявлен как выбрасывающий Exception, обработчик IOException считается достижимым: IOException является допустимым вариантом объявленного типа. Компилятор не обязан доказывать, что конкретно в данном запуске метод фактически выбросит именно IOException.
Если же try содержит только операции, не объявляющие IOException, и нет соответствующего throw, catch (IOException) недостижим. При этом catch (RuntimeException) обычно допустим, поскольку непроверяемая ошибка не обязана быть указана в сигнатуре и может возникнуть во время выполнения независимо от статического контракта.
Проверка учитывает иерархию: если тело может выбросить FileNotFoundException, достижимы обработчики FileNotFoundException, IOException, Exception и Throwable, но не обработчик несвязанного проверяемого типа, например SQLException.
Это правило не означает, что Java гарантирует отсутствие исключения во время выполнения. Ошибка может возникнуть из-за дефекта JVM, стороннего кода или другого непредвиденного механизма, но такие обстоятельства не делают недостижимый обработчик проверяемого типа корректным с точки зрения компилятора.
В сервисе чтение файла сначала выполнялось методом, объявлявшим IOException, поэтому слой приложения перехватывал её и преобразовывал в доменную ошибку. Позже реализацию заменили на кэш: новый метод возвращал готовые данные и больше не объявлял IOException, но старый catch оставили.
Рассматривались два варианта. Можно было заменить обработчик на RuntimeException, но это изменило бы смысл контракта и не обеспечивало обработку ошибки чтения. Можно было убрать устаревший catch и перенести преобразование ошибки туда, где действительно выполняется файловый ввод.
Выбран второй вариант: обработчик разместили на границе файлового адаптера, а сервис стал работать с доменным исключением. Это устранило ошибку компиляции, сохранило корректный контракт и не замаскировало реальные ошибки слишком общим обработчиком.
catch проверяемого исключения достижимым, если внутри try вызывается метод с throws Exception?Да. Объявленный тип Exception включает IOException и другие его подклассы, поэтому обработчик IOException считается потенциально применимым. Компилятор использует объявленный контракт метода, а не пытается предсказать фактический тип исключения.
catch (Exception) разрешён даже у try, в котором нет явно объявленных проверяемых исключений?Потому что Exception включает RuntimeException. Непроверяемые исключения не обязаны быть указаны в throws и могут возникнуть, например, из-за обращения к null или некорректного индекса. Поэтому такой обработчик статически не считается недостижимым, хотя чрезмерное его использование может скрывать ошибки.
throws?Технически да: если вспомогательный метод объявляет, например, throws Exception, компилятор разрешит обработчики конкретных подклассов, совместимых с этим типом. Но это ухудшает контракт — вызывающий код получает избыточно широкий список возможных ошибок. Лучше объявлять минимально необходимый тип исключения или преобразовывать низкоуровневую ошибку на подходящем архитектурном уровне.