Как обернуть проверенные исключения, но сохранить исходные исключения во время выполнения в 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);
    }
}

Тем не менее, ваше решение выглядит лучше.

Другие вопросы по тегам