кидає виняток, нарешті, блокує


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
Функція зручна, якщо вам потрібно використовувати ідіому в декількох місцях в одному класі.
Даррон

Перевірка на null є зайвою. Якщо ресурс був недійсним, тоді метод виклику порушено, слід виправити. Крім того, якщо ресурс недійсний, це, ймовірно, має бути зареєстровано. Інакше це призводить до того, що потенційний виняток мовчки ігнорується.
Дейв Джарвіс

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
Ви можете зробити цей метод більш загальним за допомогою загальнодоступної статичної порожнечі закрити Quietly (Закриваючий закритий) {
Peter Lawrey

6
Так, приємно закривати. Прикро, що багато речей (на зразок ресурсів JDBC) цього не реалізують.
Даррон

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, вам більше не потрібно явно закривати ресурси в остаточному блоці, натомість ви можете використовувати синтаксис спробувати -with-ресурси. Оператор "спробу використання ресурсів" - це тест, який оголошує один або кілька ресурсів. Ресурс - це об'єкт, який необхідно закрити після закінчення програми з ним. Оператор спробу використання ресурсів забезпечує закриття кожного ресурсу в кінці оператора. Будь-який об'єкт, який реалізує java.lang.AutoCloseable, який включає всі об'єкти, які реалізують java.io.Closeable, може використовуватися як ресурс.

Припустимо наступний код:

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

Якщо трапиться якийсь виняток, метод закриття буде викликаний на кожному з цих трьох ресурсів у зворотному порядку, в якому вони були створені. Це означає, що метод закриття буде викликаний спочатку для ResultSetm, потім Statement, а в кінці для об'єкта Connection.

Важливо також знати, що будь-які винятки, які виникають при автоматичному виклику методів закриття, придушуються. Ці придушені винятки можна отримати методом getuppression (), визначеним у класі Throwable .

Джерело: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


Здається неповним, що ця відповідь не згадує про різницю в поведінці між цим підходом та тим, як працює розміщений приклад коду ОП.
Натан Х'юз

2
використання try-with-Resources кидає виняток на закриття, якщо частина блоку спробу завершується нормально, але метод закриття не робить, на відміну від коду OP. рекомендувати його як заміну без визнання зміни в поведінці видається потенційно оманливим.
Натан Х'юз

Він не кидає виняток, метод закриття автоматично викликається придушуються.
Soroosh

2
спробуйте випадок, описаний я. блок слів завершується нормально, близько кидає щось. і перечитайте сторінку, на яку ви розмістили посилання, придушення застосовується лише тоді, коли блок спроб щось кидає.
Натан Х'юз

3

Ігнорування винятків, які трапляються в блоці "нарешті", як правило, погана ідея, якщо тільки не відомо, якими будуть ці винятки та які умови вони представлятимуть. У звичайній try/finallyсхемі використання tryблок переводить речі в стан, зовнішній код якого не очікується, і finallyблок відновлює стан цих речей до того, що очікує зовнішній код. Поза зовнішнім кодом, який фіксує виняток, як правило, очікується, що, незважаючи на виняток, все було відновлено до аnormalдержава. Наприклад, припустимо, що якийсь код починає транзакцію, а потім намагається додати два записи; блок "нарешті" виконує операцію "відкат, якщо не здійснено". Абонент може бути готовий до винятку, який відбудеться під час виконання другої операції "додавання", і може очікувати, що якщо він виловить такий виняток, база даних буде в стані, в якому вона була до спроби будь-якої операції. Якщо ж під час відкату трапляється другий виняток, можуть трапитися погані випадки, якщо абонент зробить якісь припущення щодо стану бази даних. Відмова відката представляє собою велику кризу - ту, яку не слід сприймати за кодом, очікуючи простого виключення "Не вдалося додати запис".

Моя особиста схильність полягала б у тому, щоб остаточно вилучити винятки з методу, які трапляються, і загорнути їх у "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

Чому ви хочете уникати додаткового блоку? Оскільки, нарешті, блок містить "звичайні" операції, які можуть кинути виняток І ви хочете, щоб блок, нарешті, запустився повністю, ВАМ ВИДАЛИТИ винятки.

Якщо ви не очікуєте, що блок нарешті викине виняток, і ви не знаєте, як в будь-якому разі поправити виняток (ви просто скиньте стек стека), нехай виняток міхурить стек виклику (видаліть спробу-улов нарешті блок).

Якщо ви хочете зменшити набір тексту, ви могли б реалізувати "глобальний" зовнішній блок "try-catch", який охопить усі винятки, кинуті в остаточні блоки:

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

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

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

2
-1 Для цього теж. Що робити, якщо ви намагаєтесь закрити кілька ресурсів в одному остаточному блоці? Якщо закриття першого ресурсу не вдасться, інші залишатимуться відкритими, коли буде виключено виняток.
Програматор поза законом

Ось чому я сказав Павлу, що ВИ ВДАЛИТИ винятки, якщо хочете переконатися, що блок нарешті завершився. Прочитайте ЦІЛУ відповідь!
Едуард Вірч

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 у блоці спробу, саме для цього блокуються. Також зауважте, що ви не обробляєте жодних винятків, викинутих під час "робити щось фантазійне", що насправді, я думаю, я вважаю за краще, щоб обробляти інфраструктурні винятки на більш високому рівні програми внизу стеку.
Павло

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

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
  }
}

Крім того, ви, мабуть, збираєте лише винятки, з яких ви можете відновити, якщо ви не можете відновити їх, тоді нехай він поширюється на найвищий рівень вашої програми. Якщо ви не можете перевірити на предмет помилки, вам доведеться оточити свій код блоком спробувати, як ви вже зробили (хоча я б рекомендував все-таки вводити конкретні, очікувані помилки).


Випробування умов помилок, як правило, є хорошою практикою, просто тому, що винятки дорогі.
Дірк Волмар

"Оборонне програмування" - це застаріла парадигма. Роздутий код, який є результатом тестування на всі умови помилок, врешті-решт викликає більше проблем, ніж вирішує. TDD та поводження з винятками полягає в тому, що сучасний підхід IMHO
Joe Soul-donosiй

@Joe - Я не погоджуюся з вами на тестуванні на всі умови помилок, але іноді це має сенс, особливо зважаючи на різницю (як правило) у вартості простої перевірки, щоб уникнути виключення проти самого винятку.
Кен Хендерсон

1
-1 Тут ресурс.Close () може кинути виняток. Якщо вам потрібно закрити додаткові ресурси, виняток призведе до повернення функції, і вони залишаться відкритими. Це мета другої спроби / спіймання в ОП.
Програматор поза законом

@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
Що робити, якщо використання (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

Я зіткнувся з ситуацією, подібною до якої я не міг використати спробу з ресурсами, але я також хотів обробити виняток, що надходить із закриття, а не просто увійти в систему і проігнорувати його, як це зробити механізм 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.