Можно ли выбросить checked исключение без блока try-catch и без throws в Java
Что проверяют
Понимаете ли вы, что checked исключения существуют только для компилятора, а JVM их не различает. Следом спрашивают, где это встречается в реальном коде и чем опасно.
Как отвечать
Да, можно. Проверку throws выполняет javac, в байткоде такого ограничения нет, и обойти его можно несколькими способами:
- sneaky throw через дженерики: метод объявлен как
static <E extends Throwable> void sneakyThrow(Throwable e) throws E { throw (E) e; }. При вызовеsneakyThrow(new IOException())компилятор выводитEкакRuntimeException, приведение стирается, и в рантайме летит настоящийIOException; @SneakyThrowsиз Lombok: тот же приём, только код генерируется при сборке;- другие JVM-языки: в Kotlin checked исключений нет, и функция на Kotlin спокойно выбросит
IOExceptionв вызывающий её Java-код; - рефлексия: устаревший
Class.newInstance()пробрасывает исключение конструктора как есть, в отличие отConstructor.newInstance(), который заворачивает его вInvocationTargetException.
Честный способ для продакшн-кода это обёртка: throw new UncheckedIOException(e) или своё исключение от RuntimeException с сохранённой причиной. Особенно часто это нужно в лямбдах, потому что Function и Consumer checked исключения не объявляют.
Стоит сказать и о том, чего сделать нельзя: throw new IOException() в статическом блоке инициализации не скомпилируется, язык запрещает инициализатору выбрасывать checked исключения.
Где спотыкаются
Отвечают про собственный наследник RuntimeException. Он unchecked, так что вопрос остаётся без ответа.
Не знают о последствиях для вызывающего кода. Написать catch (IOException e) вокруг метода, который его не объявляет, нельзя: компилятор скажет, что в блоке try такое исключение не выбрасывается. Ловить приходится Exception.
Забывают про транзакции в Spring. @Transactional по умолчанию откатывает транзакцию только на RuntimeException и Error, поэтому тайком выброшенный IOException пройдёт без отката, и в базе останется половина изменений.