При каких условиях Java позволяет повторно выбросить параметр catch, сохранив более узкий список проверяемых исключений в throws?
Java разрешает точный повторный выброс параметра catch, если компилятор по анализу потока управления может доказать, какие проверяемые исключения реально могут прийти из блока try, а параметр исключения не был переназначен. Поэтому вместо широкого throws Exception метод может объявить только конкретные типы, например IOException и SQLException.
До появления этой возможности обработчик, принимавший общий тип вроде Exception, часто вынуждал объявлять в сигнатуре метода тот же широкий тип. Это ухудшало контракт метода: вызывающий код не видел точный набор ожидаемых проверяемых исключений.
В Java 7 появился анализ точного повторного выброса, связанный с развитием средств обработки исключений. Он позволил централизованно логировать или выполнять общие действия в одном catch, не теряя точность декларации throws.
Предположим, метод вызывает две операции: одна может выбросить IOException, другая — SQLException. Общая обработка в catch (Exception) удобна, но декларация throws Exception заставляет вызывающий код учитывать практически любое проверяемое исключение.
Если заменить широкий контракт на конкретные типы без понимания механизма, код либо не скомпилируется, либо потребует раздельных обработчиков. Ошибочная переназначенная переменная исключения также лишает компилятор возможности доказать его исходный тип.
Компилятор анализирует проверяемые исключения, которые могут выйти из try, и сопоставляет их с типом параметра catch. Если параметр не изменяется, выражение throw e трактуется не просто как выброс объявленного типа Exception, а как повторный выброс фактических проверяемых типов, пришедших из try.
Здесь process не обязан объявлять throws Exception: компилятор выводит IOException и SQLException. Непроверяемые исключения, например NullPointerException, могут возникнуть дополнительно, но их не требуется указывать в throws.
Ключевое ограничение — параметр catch нельзя переназначать перед повторным выбросом. После присваивания в него другого объекта компилятор уже не может безопасно считать, что выбрасывается исходное исключение из try, поэтому обычно требуется объявить более широкий тип.
Точный повторный выброс отличается от создания нового исключения. При throw new ServiceException(e) выбрасывается новый тип, и именно его нужно учитывать в контракте; исходная причина при этом сохраняется только как причина (cause), а не как тип повторно выброшенного объекта.
Преимущество подхода — точный API-контракт при единой логике обработки. Компромисс заключается в том, что контракт может измениться, если набор исключений внутри try расширится: компилятор потребует обновить throws или обработку.
В слое доступа к данным нужно записывать все ошибки в журнал, но сервисный слой должен различать ошибки чтения файла и ошибки SQL. Были рассмотрены три варианта.
Раздельные catch для каждого типа дают максимальную явность, но дублируют журналирование. catch (Exception) с объявлением throws Exception убирает дублирование, однако чрезмерно расширяет контракт и ухудшает подсказки IDE и обработку ошибок вызывающим кодом.
Выбран общий catch с неизменяемым параметром и точным повторным выбросом. В результате журналирование осталось централизованным, а сигнатура метода сохранила только IOException и SQLException; сервисный слой смог обрабатывать их по отдельности.
Что изменится, если параметр catch переназначить перед throw?
Точный вывод типов перестаёт быть безопасным: в переменной может находиться уже не исключение, пришедшее из try. Поэтому компилятор рассматривает повторный выброс через объявленный тип параметра, например Exception, и может потребовать добавить его в throws либо выполнить более узкую обработку.
Работает ли точный повторный выброс, если из try вызывается метод, объявляющий throws Exception?
Нет, компилятор не сможет сузить такой контракт до конкретных типов, если у него нет дополнительной информации. Для точного вывода нужно, чтобы вызываемые операции сами объявляли конкретные проверяемые исключения или чтобы они были иным образом исключены из возможного набора.
Сохраняет ли оборачивание исключения тот же эффект, что и точный повторный выброс?
Нет. При создании нового исключения меняется объект и его тип: вызывающий код видит новый тип, указанный после new. Исходное исключение можно передать как причину для диагностики, но оно уже не определяет проверяемый контракт метода; в throws указывается тип нового исключения, если он проверяемый.