Можно ли определить, произошло ли исключение до того, как я вошел в блок finally?

Есть ли в Java элегантный способ определить, произошло ли исключение до запуска блока finally? При работе с операторами close() обычно требуется обработка исключений в блоке finally. В идеале мы хотели бы сохранить оба исключения и распространять их (поскольку оба они могут содержать полезную информацию). Единственный способ, которым я могу придумать, это сделать переменную вне области действия try-catch-finally, чтобы сохранить ссылку на выброшенное исключение. Затем распространите "сохраненное" исключение на любое, которое встречается в блоке finally.

Есть ли более элегантный способ сделать это? Возможно вызов API, который покажет это?

Вот примерный код того, о чем я говорю:

Throwable t = null; 
try {   
   stream.write(buffer); 
} catch(IOException e) {
    t = e;   //Need to save this exception for finally
    throw e;
} finally {   
    try {
       stream.close();   //may throw exception
   } catch(IOException e) {
      //Is there something better than saving the exception from the exception block?
      if(t!=null) {
         //propagate the read exception as the "cause"--not great, but you see what I mean.
         throw new IOException("Could not close in finally block: " + e.getMessage(),t);
      } else {
         throw e;  //just pass it up
      }    
   }//end close
}

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

Может быть что-то вроде Thread.getPendingException() или что-то подобное? В этом отношении, есть ли элегантное решение на других языках?

Этот вопрос фактически породил комментарии в другом вопросе, который поднял интересный вопрос.

5 ответов

Решение

Ваша идея об установке переменной вне области действия try/catch/finally верна.

Не может быть более одного исключения, распространяющегося одновременно.

Вместо использования логического флага я бы сохранил ссылку на объект Exception. Таким образом, у вас есть не только способ проверить, произошло ли исключение (объект будет нулевым, если не возникло исключение), но вы также получите доступ к самому объекту исключения в вашем блоке finally, если исключение действительно произошло. Вам просто нужно помнить, чтобы установить объект ошибки во всех ваших блоках перехвата (если вы сбросите ошибку).

Я думаю, что это недостающая возможность языка C#, которую следует добавить. Блок finally должен поддерживать ссылку на базовый класс Exception, аналогично тому, как его поддерживает блок catch, чтобы ссылка на распространяющееся исключение была доступна для блока finally. Это было бы простой задачей для компилятора, избавившей нас от необходимости вручную создавать локальную переменную Exception и помнить о необходимости вручную устанавливать ее значение перед повторной выдачей ошибки, а также предотвращать ошибку при установке переменной Exception при не выкидывать ошибку (помните, что это только неперехваченные исключения, которые мы хотим сделать видимыми в блоке finally).

finally (Exception main_exception)
{
    try
    {
        //cleanup that may throw an error (absolutely unpredictably)
    }
    catch (Exception err)
    {
        //Instead of throwing another error,
        //just add data to main exception mentioning that an error occurred in the finally block!
        main_exception.Data.Add( "finally_error", err );
        //main exception propagates from finally block normally, with additional data
    }
}

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

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

Использовать логирование...

try {   
   stream.write(buffer); 
} catch(IOException ex) {
    if (LOG.isErrorEnabled()) { // You can use log level whatever you want
        LOG.error("Something wrong: " + ex.getMessage(), ex);
    }
    throw ex;
} finally {   
    if (stream != null) {
        try {
            stream.close();
        } catch (IOException ex) {
            if (LOG.isWarnEnabled()) {
                LOG.warn("Could not close in finally block", ex);
            }
        }
    }
}

В vb.net можно использовать оператор "Catch...When" для захвата исключения в локальную переменную без его фактического перехвата. Это имеет ряд преимуществ. Среди них:

  1. Если ничто не собирается "в конечном итоге" перехватить исключение, необработанная ловушка исключения будет запущена с места исходного исключения. Гораздо приятнее, чем ловушка отладчика при последнем повторном отбрасывании, тем более что информация, которая может понадобиться для отладки, еще не вышла за рамки или не была сметена утверждениями "наконец".
  2. Хотя rethrow не очистит трассировку стека, как это делает Throw Ex, он все равно будет часто сглаживать трассировку стека. Если исключение не перехвачено, трассировка стека будет чистой.

Поскольку эта функция не поддерживается в vb, может быть полезно написать обертку vb для реализации кода на C (например, с учетом MethodInvoker и Action(Of Exception), выполнить MethodInvoker с помощью "Try" и Action in a ". В заключение".

Одна интересная изюминка: для Catch-When возможно увидеть исключение, которое в конечном итоге будет перезаписано исключением условия finally. В некоторых случаях это может быть хорошо; в других случаях это может сбивать с толку. В любом случае, это то, что нужно знать.

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