Как обернуть проверенные исключения, но сохранить исходные исключения во время выполнения в Java
У меня есть код, который может генерировать как проверенные, так и исключения во время выполнения.
Я хотел бы поймать проверенное исключение и обернуть его исключением во время выполнения. Но если выдается исключение RuntimeException, мне не нужно его оборачивать, поскольку это уже исключение времени выполнения.
Решение, которое у меня есть, имеет некоторые накладные расходы и не является "аккуратным":
try {
// some code that can throw both checked and runtime exception
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
Есть идеи для более элегантного образа?
8 ответов
Я использую "слепой" пересчет, чтобы пропустить проверенные исключения. Я использовал это для прохождения через Streams API, где я не могу использовать лямбда-выражения, которые выдают проверенные исключения. Например, у нас есть функциональные интерфейсы ThrowingXxxxx, поэтому проверенное исключение можно пропустить.
Это позволяет мне перехватить проверенное исключение в вызывающем абоненте, естественно, без необходимости знать, что вызываемый абонент должен был пропустить его через интерфейс, который не допускал проверенных исключений.
try {
// some code that can throw both checked and runtime exception
} catch (Exception e) {
throw rethrow(e);
}
В вызывающем методе я снова могу объявить проверенное исключение.
public void loadFile(String file) throws IOException {
// call method with rethrow
}
/**
* Cast a CheckedException as an unchecked one.
*
* @param throwable to cast
* @param <T> the type of the Throwable
* @return this method will never return a Throwable instance, it will just throw it.
* @throws T the throwable as an unchecked throwable
*/
@SuppressWarnings("unchecked")
public static <T extends Throwable> RuntimeException rethrow(Throwable throwable) throws T {
throw (T) throwable; // rely on vacuous cast
}
Существует множество различных вариантов обработки исключений. Мы используем несколько из них.
https://vanilla-java.github.io/2016/06/21/Reviewing-Exception-Handling.html
Гуава-х Throwables.propagate()
делает именно это:
try {
// some code that can throw both checked and runtime exception
} catch (Exception e) {
throw Throwables.propagate(e);
}
ОБНОВЛЕНИЕ: этот метод устарел. Смотрите эту страницу для подробного объяснения.
На самом деле, нет.
Если вы делаете это много, вы можете убрать это во вспомогательный метод.
static RuntimeException unchecked(Throwable t){
if (t instanceof RuntimeException){
return (RuntimeException) t;
} else if (t instanceof Error) { // if you don't want to wrap those
throw (Error) t;
} else {
return new RuntimeException(t);
}
}
try{
// ..
}
catch (Exception e){
throw unchecked(e);
}
У меня есть специально скомпилированный файл.class, содержащий следующее:
public class Thrower {
public static void Throw(java.lang.Throwable t) {
throw t;
}
}
Это просто работает. Компилятор java обычно отказывается компилировать это, но верификатор байт-кода не заботится вообще.
Класс используется аналогично ответу Питера Лоури:
try {
// some code that can throw both checked and runtime exception
} catch (Exception e) {
Thrower.Throw(e);
}
Проблема в том, что Exception
слишком широк. Вы должны точно знать, каковы возможные проверенные исключения.
try {
// code that throws checked and unchecked exceptions
} catch (IOException | SomeOtherException ex) {
throw new RuntimeException(ex);
}
Причины, по которым это не сработает, показывают более глубокие проблемы, которые следует решать вместо этого:
Если метод объявляет, что это throws Exception
тогда оно слишком широкое. Зная, что "что-то может пойти не так" без дополнительной информации, бесполезно для звонящего. Метод должен использовать определенные классы исключений в значимой иерархии или, если необходимо, использовать непроверенные исключения.
Если метод выдает слишком много различных типов проверенных исключений, это слишком сложно. Его следует либо реорганизовать в несколько более простых методов, либо исключения следует упорядочить в разумной иерархии наследования, в зависимости от ситуации.
Конечно, могут быть исключения из правил. Объявление метода throws Exception
может быть вполне разумным, если он используется какой-то сквозной структурой (такой как JUnit или AspectJ или Spring), а не содержит API для использования другими.
Обычно я использую структуру кода того же типа, но сокращаю ее до одной строки за один из немногих случаев, когда троичный оператор действительно делает код лучше:
try {
// code that can throw
}
catch (Exception e) {
throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e);
}
Это не требует дополнительных методов или catch
блоки, поэтому мне это нравится.
lombok обрабатывает это с помощью простой аннотации к методу
Пример:
import lombok.SneakyThrows;
@SneakyThrows
void methodThatUsusallyNeedsToDeclareException() {
new FileInputStream("/doesn'tMatter");
}
В примере метод должен был быть объявлен
throws FileNotFoundException
, но с
@SneakyThrows
аннотация, это не так.
Что на самом деле происходит за кулисами, так это то, что ломбок делает тот же трюк, что и высоко оцененный ответ на тот же вопрос.
Миссия выполнена!
Вы можете переписать то же самое, используя оператор instanceof
try {
// some code that can throw both checked and runtime exception
} catch (Exception e) {
if (e instanceof RuntimeException) {
throw e;
} else {
throw new RuntimeException(e);
}
}
Тем не менее, ваше решение выглядит лучше.