Насколько безопасно мое безопасное ретронирование?

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


Довольно часто я нахожусь в ситуациях, выглядящих так:

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

В С# вы можете сделать это следующим образом:

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, как правило, нахмурился, я не очень-то счастлив в использовании рефлексии только для того, чтобы реконструировать исключение, и я всегда чувствую немного неловкое письмо "это не произойдет", но в практика работает хорошо (или, по крайней мере, кажется). Я задаюсь вопросом:

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

Edit:

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

Ответ 1

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

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, которые я забыл.

Ответ 2

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

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

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

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

Ответ 3

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

Недостатком этого кода является то, что он менее переносимый (он работает для 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;
      }