выбрасывает Exception в блоки finally


100

Есть ли элегантный способ обработки исключений, возникающих в finallyблоке?

Например:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

Как избежать try/ catchв finallyблоке?

Ответы:


72

Я обычно так делаю:

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

В другом месте:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}

4
Да, я использую очень похожую идиому. Но я не создаю для этого функцию.
OscarRyz

9
Функция удобна, если вам нужно использовать идиому в нескольких местах одного класса.
Darron

Проверка на нуль избыточна. Если ресурс был нулевым, то вызывающий метод не работает, следует исправить. Кроме того, если ресурс равен нулю, это, вероятно, должно быть зарегистрировано. В противном случае это приведет к молчаливому игнорированию потенциального исключения.
Дэйв Джарвис,

14
Проверка на null не всегда избыточна. Думайте о "resource = new FileInputStream (" file.txt ")" как о первой строке попытки. Кроме того, этот вопрос не касался аспектно-ориентированного программирования, которым многие люди не пользуются. Однако концепция о том, что исключение не следует просто игнорировать, была наиболее компактно реализована путем отображения оператора журнала.
Даррон

1
Resource=> Closeable?
Дмитрий Гинзбург

25

Обычно я использую один из следующих closeQuietlyметодов org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}

3
Вы можете сделать этот метод более общим с помощью Closeable public static void closeQuietly (Closeable closeable) {
Питер Лоури,

6
Да, Closeable хорош. Жаль, что многие вещи (например, ресурсы JDBC) не реализуют его.
Darron

22

Если вы используете Java 7 и resourceреализует его AutoClosable, вы можете сделать это (используя InputStream в качестве примера):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}

8

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

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

ОБНОВЛЕНИЕ: я изучил это немного больше и нашел отличное сообщение в блоге от того, кто явно думал об этом больше, чем я: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Он идет еще дальше и объединяет два исключения в одно, что, на мой взгляд , может быть полезно в некоторых случаях.


1
+1 за ссылку на блог. Вдобавок я бы хотя бы записал ignoreисключение
Денис Княжев

6

Начиная с Java 7 вам больше не нужно явно закрывать ресурсы в блоке finally, вместо этого вы можете использовать синтаксис try -with-resources. Оператор try-with-resources - это оператор try, объявляющий один или несколько ресурсов. Ресурс - это объект, который необходимо закрыть после того, как программа завершит работу с ним. Оператор try-with-resources гарантирует, что каждый ресурс будет закрыт в конце оператора. Любой объект, реализующий java.lang.AutoCloseable, который включает все объекты, реализующие java.io.Closeable, может использоваться как ресурс.

Предположим следующий код:

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

Если произойдет какое-либо исключение, метод close будет вызываться для каждого из этих трех ресурсов в порядке, обратном тому, в котором они были созданы. Это означает, что метод close будет вызываться сначала для ResultSetm, затем для оператора и в конце для объекта Connection.

Также важно знать, что любые исключения, возникающие при автоматическом вызове методов close, подавляются. Эти подавленные исключения можно получить с помощью метода getsuppressed (), определенного в классе Throwable .

Источник: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


Кажется неполным, что в этом ответе не упоминается разница в поведении между этим подходом и тем, как работает опубликованный пример кода OP.
Натан Хьюз

2
использование try-with-resources вызывает исключение при закрытии, если часть в блоке try завершается нормально, а метод close - нет, в отличие от кода OP. рекомендация его в качестве замены без признания изменения поведения может ввести в заблуждение.
Натан Хьюз

Он не генерирует исключение, автоматически вызывается метод close, подавляется.
Soroosh

2
попробуйте случай, который я описал. блок try завершается нормально, close что-то бросает. и перечитайте страницу, на которую вы разместили ссылку, подавление применяется только тогда, когда блок try что-то бросает.
Натан Хьюз,

3

Игнорирование исключений, которые происходят в блоке 'finally', как правило, является плохой идеей, если неизвестно, какими будут эти исключения и какие условия они будут представлять. В обычном try/finallyшаблоне использования tryблок переводит вещи в состояние, которого внешний код не ожидает, и finallyблок восстанавливает состояние этих вещей до того, что ожидает внешний код. Внешний код, который перехватывает исключение, обычно ожидает, что, несмотря на исключение, все было восстановлено доnormalштат. Например, предположим, что какой-то код запускает транзакцию, а затем пытается добавить две записи; блок «finally» выполняет операцию «откат, если не зафиксирован». Вызывающий может быть подготовлен к возникновению исключения во время выполнения второй операции «добавления» и может ожидать, что, если он уловит такое исключение, база данных будет в состоянии, в котором она была до попытки выполнения любой из этих операций. Если, однако, во время отката возникает второе исключение, могут произойти неприятности, если вызывающий объект сделает какие-либо предположения о состоянии базы данных. Ошибка отката представляет собой серьезную кризис, который не должен быть обнаружен кодом, ожидающим простого исключения «Не удалось добавить запись».

Лично я предпочитаю использовать метод finally, который будет перехватывать возникающие исключения и заключать их в «CleanupFailedException», признавая, что такой сбой представляет собой серьезную проблему, и такое исключение не следует воспринимать легкомысленно.


2

Одно решение, если два исключения - это два разных класса

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

Но иногда не избежать второй попытки. например, для закрытия потока

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }

В вашем случае, если вы использовали оператор using, он должен очистить ресурс.
Чак Конвей,

Плохо, я предполагаю, что это C #.
Чак Конвей,

1

Почему вы хотите избежать дополнительной блокировки? Поскольку блок finally содержит «нормальные» операции, которые могут вызывать исключение, и вы хотите, чтобы блок finally выполнялся полностью, вы ДОЛЖНЫ перехватывать исключения.

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

Если вы хотите уменьшить количество наборов текста, вы можете реализовать «глобальный» внешний блок try-catch, который будет перехватывать все исключения, созданные в блоках finally:

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}

2
-1 И на эту тоже. Что, если вы пытаетесь закрыть несколько ресурсов в одном блоке finally? Если закрыть первый ресурс не удается, остальные останутся открытыми после создания исключения.
Outlaw Programmer

Вот почему я сказал Полу, что вы ДОЛЖНЫ перехватывать исключения, если хотите, чтобы блок finally завершился. Пожалуйста, прочтите ВЕСЬ ответ!
Эдуард Вирч

1

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

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

Этот код гарантирует следующее:

  1. Ресурс освобождается, когда код завершен
  2. Исключения, возникающие при закрытии ресурса, не потребляются без их обработки.
  3. Код не пытается дважды закрыть ресурс, ненужное исключение не создается.

Вы также можете избежать вызова resource.close (); resource = null в блоке try, для этого и предназначены блоки finally. Также обратите внимание, что вы не обрабатываете какие-либо исключения, возникающие при «выполнении чего-то необычного», что на самом деле, я думаю, я предпочитаю лучше обрабатывать инфраструктурные исключения на более высоком уровне приложения вниз по стеку.
Пол

Resource.close () также может вызывать и исключение - то есть, когда сбой очистки буфера. Это исключение никогда не следует использовать. Однако при закрытии потока в результате ранее возникшего исключения ресурс должен быть тихо закрыт, игнорируя исключение и сохраняя основную причину.
Grogi

0

Если вы можете, вы должны сначала протестировать, чтобы избежать состояния ошибки.

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

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


Тестирование условий ошибки в целом является хорошей практикой просто потому, что исключения дороги.
Дирк Воллмар,

«Защитное программирование» - устаревшая парадигма. Раздутый код, возникающий в результате тестирования всех ошибок, в конечном итоге вызывает больше проблем, чем решает. TDD и обработка исключений - это современный подход ИМХО
Joe Soul -gotier

@Joe - Я не возражаю против тестирования всех условий ошибок, но иногда это имеет смысл, особенно в свете разницы (обычно) в стоимости простой проверки, позволяющей избежать исключения, по сравнению с самим исключением.
Кен Хендерсон,

1
-1 Здесь resource.Close () может вызвать исключение. Если вам нужно закрыть дополнительные ресурсы, исключение приведет к возврату функции, и они останутся открытыми. Это цель второй попытки / улова в OP.
Outlaw Programmer

@Outlaw - вы упускаете мою точку зрения, если Close выдает исключение, а ресурс открыт, а затем путем захвата и подавления исключения, как мне исправить проблему? Поэтому я позволяю ему распространяться (довольно редко я могу восстановиться, пока он еще открыт).
Кен Хендерсон

0

Вы можете преобразовать это в другой метод ...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}

0

Я обычно так делаю:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

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

Это один из случаев, когда, по крайней мере, для меня безопасно игнорировать это проверенное исключение.

По сей день у меня не было проблем с использованием этой идиомы.


Я бы записал это на всякий случай, если в будущем вы обнаружите утечки. Таким образом, вы будете знать, откуда они (не) могут прийти
Эгвор,

@Egwor. Я согласен с тобой. Это был всего лишь небольшой кусочек. Я тоже регистрирую это и, возможно, использую уловку, что-то можно было бы сделать за исключением :)
OscarRyz

0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Работа выполнена. Нет нулевых тестов. Единый улов, включая исключения для получения и выпуска. Конечно, вы можете использовать идиому Execute Around, и вам нужно написать ее только один раз для каждого типа ресурса.


5
Что делать, если use (resource) генерирует исключение A, а затем resource.release () генерирует исключение B? Исключение А потеряно ...
Даррон

0

Переход Resourceот лучшего ответа кCloseable

Реализует потоки CloseableТаким образом, вы можете повторно использовать метод для всех потоков

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}

0

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

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.