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

Есть идеи более элегантного способа?


person AlikElzin-kilaka    schedule 27.09.2016    source источник
comment
Вот и все. Единственное улучшение — это функция более высокого порядка, которая принимает лямбда-выражение, являющееся телом try, и оборачивает его этой логикой. Вы можете проверить эту тему: stackoverflow.com/questions/31270759/   -  person Marko Topolnik    schedule 27.09.2016
comment
Я считаю, что это более элегантный способ сделать это.   -  person Bnrdo    schedule 27.06.2019


Ответы (7)


Я использую «слепой» повторный вызов, чтобы пропустить проверенные исключения. Я использовал это для прохождения через 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

person Peter Lawrey    schedule 27.09.2016
comment
Таким образом, это на самом деле вызывает IOException (из rethrow) во время выполнения, но компилятор думает, что это непроверенное исключение (потому что вы сказали, что приведете его таким образом, даже если приведение было стерто)? - person Thilo; 27.09.2016
comment
@Thilo Во время выполнения нет разницы между проверенным и непроверенным для JVM, и есть несколько способов обмануть компилятор, которые работают во время выполнения. Приведение с использованием дженериков — самое простое, что я чувствую. - person Peter Lawrey; 27.09.2016
comment
Подлый. Мне это нравится. :-) (и да, не иметь исключений от лямбд неудобно). - person Thilo; 27.09.2016
comment
Sneaky - это полуофициальное название для этого :) - person Marko Topolnik; 27.09.2016
comment
@MarkoTopolnik - вежливое название для него. Есть также слово, которое ритмично сочетается с tack. ;) - person Peter Lawrey; 27.09.2016
comment
Преуспевает в Java 8, но не в Java 7. Получение Unhandled exception: java.lang.Throwable в Java 7. Я что-то здесь упустил? - person AlikElzin-kilaka; 27.09.2016
comment
@AlikElzin-kilaka Да, этот синтаксис работает только с новыми правилами вывода в Java 8. Для Java 7 требуется более длинная идиома с двумя методами. - person Marko Topolnik; 27.09.2016
comment
@AlikElzin-kilaka в java 7 вы можете сделать Thread.currentThread().stop(e); - person Peter Lawrey; 27.09.2016
comment
Но это нарушило бы прямую совместимость, поскольку stop() больше не поддерживается, а начиная с Java 8 он просто выдает UnsupportedOperationException. - person Marko Topolnik; 27.09.2016
comment
@MarkoTopolnik действительно. Мы используем только Java 8, так что это не проблема. - person Peter Lawrey; 27.09.2016
comment
Почему компилятор не жалуется на throws T? Разве он не должен сделать вывод, что T является Throwable? - person Margaret Bloom; 27.09.2016
comment
@MargaretBloom хороший вопрос. Он не знает, проверено ли T или нет, но разрешает это. Примечание Throwable проверяется нормально. - person Peter Lawrey; 27.09.2016
comment
@MargaretBloom Существует специальное постановление введен в спецификации Java 8. - person Marko Topolnik; 27.09.2016
comment
Что вы делаете на месте ловли? Если вы просто напишете catch (IOException e) и ни один код в блоке try не сможет генерировать проверенные исключения, он будет содержать сообщение о том, что код в блоке catch недоступен, верно? - person Tavian Barnes; 27.09.2016
comment
@TavianBarnes, если он вызывается внутри метода, который throws проверенное исключение, вы можете поймать его вне вызова метода. - person Peter Lawrey; 27.09.2016
comment
@TavianBarnes в худшем случае вам нужно написать if (false) throw new CheckedException();, однако обертывание кода в метод делает его более понятным, имхо. - person Peter Lawrey; 27.09.2016
comment
@PeterLawrey throw rethrow(e); зачем лишний throw? однако в байт-коде он будет ATHROW заменен на POP, если удалить throw, такого же размера. - person Mikhail Boyarsky; 12.05.2017
comment
@MikhailBoyarsky, чтобы было понятнее, что что-то будет брошено, а не простой вызов метода. - person Peter Lawrey; 14.05.2017

Throwables.propagate() делает именно это:

try {
    // some code that can throw both checked and runtime exception
} catch (Exception e) {
    throw Throwables.propagate(e);
}

ОБНОВЛЕНИЕ: этот метод устарел. Подробнее см. на этой странице.

person shmosel    schedule 27.09.2016
comment
Не по теме: сегодня я (надеюсь) доберусь до легендарного золота... просто говорю спасибо всем людям, у которых я так многому научился... - person GhostCat; 07.09.2017

Не совсем.

Если вы делаете это часто, вы можете спрятать его во вспомогательный метод.

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);
}
person Thilo    schedule 27.09.2016
comment
изменить catch (Exception e) на catch (Throwable e) ? - person Jason S; 27.09.2016
comment
@JasonS: Вы могли бы, если вы также хотите поймать Error (что не рекомендуется). Я бы просто оставил их незамеченными. Компилятор не будет жаловаться на это. - person Thilo; 28.09.2016
comment
Я думаю, что смысл был в том, что нет причин использовать Throwable и иметь особый случай для Error, если вы ловите только Exception. - person OrangeDog; 01.10.2016
comment
@OrangeDog, наверное, слишком много слабой связи, да. Этот метод имеет более широкое применение, чем это требуется для вызывающего кода примера. Но это то же самое для многих утилит обработки исключений, например уже упомянутой Guava Throwables.propagate или Peter's rethrow. - person Thilo; 01.10.2016

У меня есть специально скомпилированный файл .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);
}
person Joshua    schedule 27.09.2016
comment
Как вы его скомпилировали? - person shmosel; 27.09.2016
comment
Я отредактировал исходный код javac, чтобы выбить проверку, скомпилировал javac и использовал этот javac для компиляции файла .class. На результирующий файл .class могут ссылаться компиляторы .java, скомпилированные обычным javac. - person Joshua; 27.09.2016
comment
Вау, не то, что я ожидал. Файл доступен где-нибудь? - person shmosel; 27.09.2016
comment
Вот инструкции. Я не уверен, но я думаю, что постер в блоге получил их из моих оригинальных инструкций по старому репозиторию ошибок Java от Sun. blog.bangbits.com/2009/08/tweaking-javac-leniency. html Если репозиторий ошибок все еще существует, файл .class находится там. - person Joshua; 27.09.2016
comment
Даже хитрее, чем ответ Питера. Мне это нравится. - person Thilo; 01.10.2016

Вы можете переписать то же самое, используя оператор 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);
    }
}

Однако ваше решение выглядит лучше.

person Tionio    schedule 27.09.2016

Проблема в том, что 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 для использования другими.

person OrangeDog    schedule 27.09.2016
comment
Его метод не объявляет, что он выдает Exception, он хочет элегантно поймать, если что-то дальше по стеку вызовов выдает исключение, которое может включать любое типизированное исключение, объявленное методами, которые он напрямую вызывает, или что-то еще вроде NPE или что-то еще, что может пойти не так. Вполне разумно. - person user467257; 27.09.2016
comment
@ user467257 да, я понимаю ОП. Я указываю, что мой предложенный шаблон не будет работать, если код tryd является методом, который throws Exception, но это указывает на то, что этот метод плохо спроектирован. - person OrangeDog; 27.09.2016

Обычно я использую один и тот же тип структуры кода, но сокращаю ее до одной строки в одном из немногих случаев, когда тернарный оператор действительно делает код лучше:

try {
  // code that can throw
}
catch (Exception e) {
  throw (e instanceof RuntimeException) ? (RuntimeException) e : new RuntimeException(e);
}

Это не требует дополнительных методов или блоков catch, поэтому мне это нравится.

person Community    schedule 27.09.2016