Насколько безопасен мой безопасный переброс? - PullRequest
11 голосов
/ 30 октября 2009

( Позднее редактирование: Этот вопрос, мы надеемся, устареет, когда выйдет Java 7, из-за функции "окончательного отбрасывания" , которая, , кажется, будет добавлен .)


Довольно часто я нахожусь в ситуациях, подобных этой:

    do some initialization
    try {
        do some work 
    } catch any exception {
        undo initialization
        rethrow exception
    }

В C # вы можете сделать это так:

InitializeStuff();
try
{
    DoSomeWork();
}
catch 
{
    UndoInitialize();
    throw;
}

Для Java нет хорошей замены, и, поскольку предложение по улучшенной обработке исключений было вырезано из Java 7 , похоже, что в лучшем случае потребуется несколько лет, пока мы не получим нечто подобное. Таким образом, я решил свернуть свое собственное:

( Редактировать: Через полгода, окончательный повторный бросок вернулся , или так кажется.)

public final class Rethrow {

    private Rethrow() { throw new AssertionError("uninstantiable"); }

    /** Rethrows t if it is an unchecked exception. */
    public static void unchecked(Throwable t) {
        if (t instanceof Error)
            throw (Error) t;
        if (t instanceof RuntimeException)
            throw (RuntimeException) t;
    }

    /** Rethrows t if it is an unchecked exception or an instance of E. */
    public static <E extends Exception> void instanceOrUnchecked(
            Class<E> exceptionClass, Throwable t) throws E, Error,
            RuntimeException {
        Rethrow.unchecked(t);
        if (exceptionClass.isInstance(t))
            throw exceptionClass.cast(t);
    }

}

Типичное использование:

public void doStuff() throws SomeException {
    initializeStuff();
    try {
        doSomeWork();
    } catch (Throwable t) {
        undoInitialize();
        Rethrow.instanceOrUnchecked(SomeException.class, t);
        // We shouldn't get past the above line as only unchecked or 
        // SomeException exceptions are thrown in the try block, but
        // we don't want to risk swallowing an error, so:
        throw new SomeException("Unexpected exception", t); 
    }
    private void doSomeWork() throws SomeException { ... }
}

Это немного многословно, ловить Throwable обычно не одобряется, я не очень рад использовать рефлексию, чтобы просто выбросить исключение, и я всегда чувствую себя немного неловко, когда пишу комментарии "этого не произойдет", но в На практике это работает хорошо (или, кажется, по крайней мере). Что мне интересно, так это:

  1. Есть ли у меня недостатки в методах помощника rethrow? Какие угловые случаи я пропустил? (Я знаю, что Throwable мог быть вызван чем-то настолько серьезным, что мой undoInitialize потерпит неудачу, но это нормально.)
    • Кто-то уже это придумал? Я посмотрел на ExceptionUtils Commons Lang, но это делает другие вещи.

Edit:

  • finally это не тот дроид, которого я ищу. Мне интересно делать что-то, только когда выдается исключение.
  • Да, я знаю, что ловить Throwable - это большое нет-нет, но я думаю, что это меньшее зло по сравнению с тем, чтобы иметь три предложения catch (для Error, RuntimeException и SomeException, соответственно) с одинаковыми код.
  • Обратите внимание, что я не пытаюсь подавить какие-либо ошибки - идея состоит в том, что любые исключения, сгенерированные в блоке try, будут продолжать всплывать через стек вызовов, как только я перезаписал несколько вещей.

Ответы [ 3 ]

5 голосов
/ 01 ноября 2009

Есть несколько способов справиться с этим. Во-первых, я предпочитаю, если вам не нужно знать, что это за исключение.

boolean okay = false;
try {
  // do some work which might throw an exception
  okay = true;
} finally {
  if (!okay) // do some clean up.
}

В некоторых случаях вы можете сделать то же самое без дополнительной переменной, в зависимости от того, что делает блок try.

Второй вариант - взлом, но также работает.

try {
    // do some work which might throw an exception
} catch (Throwable t) {
    // do something with t.
    Thread.currentThread().stop(t);
}

Метод stop (Throwable t) не останавливает поток, а заставляет поток генерировать исключение, предоставленное непроверенным способом.

Вы можете использовать Unsafe.throwException (), немного поиграв, и есть способ сделать это с помощью Generics, о которых я забыл.

1 голос
/ 30 октября 2009

Альтернативой является фабрика, которая создает SomeException, только если причиной является проверенное исключение:

   public static SomeException throwException(String message, Throwable cause) throws SomeException {
      unchecked(cause); //calls the method you defined in the question.
      throw new SomeException(message, cause);
   }

Причина, по которой я вставил возвращаемое значение в метод, заключается в том, что клиент может сделать что-то вроде этого:

     catch (Throwable e) {
         undoInitialize();
         throw SomeException.throwException("message", e);
     }

так, чтобы компилятор был обманут, не требуя возврата после оператора catch, если у метода есть тип возвращаемого значения, но он по-прежнему выдает исключение, если клиент забыл поставить throw перед вызовом фабричного метода.

Недостаток этого перед вашим кодом в том, что он менее переносим (он работает для SomeException, но не для SomeOtherException), но это может быть хорошо, потому что это будет не для каждого типа исключения, который вам нужен. отменить инициализацию.

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

      public SomeException(message, cause) {
            super(message, unchecked(cause));
      }

      private static Throwable unchecked(Throwable cause) {
          if (cause instanceof Error) throw (Error) cause;
          if (cause instanceof RuntimeException) throw (RuntimeException) cause;
          return cause;
      }
1 голос
/ 30 октября 2009

Если вас беспокоит, что ваша деинициализация может произойти, вы можете просто поместить этот код в блок finally, как, если бы он вызывался в какой-то момент, возможно, вам всегда следует очистить.

Я опасаюсь поймать Throwable как некоторые из исключений, которые я хочу обработать, а некоторые я просто регистрирую, так как нет смысла передавать исключения, с которыми пользователь ничего не может поделать, например NullPointerException.

Но вы не показали, как определяется SomeException, но если выбрасывается OutOfMemoryException, ваш бросаемый объект поймает его, но он может не совпадать с типом SomeException, поэтому ваша оболочка будет необходимо в вашей функции выборки, по крайней мере, когда я смотрю на метод instanceOrUnchecked.

Возможно, вы захотите написать модульный тест, попробовать разные классы исключений и посмотреть, что работает или не работает, как ожидалось, чтобы вы могли задокументировать ожидаемое поведение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...