В чем причина этого предложения finally, содержащего вызовы close()

Я изучаю онлайн-курс по Java Введение в программирование с использованием Java.

В главе, посвященной вводу-выводу, приведенный ниже код представлен следующим оператором:

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

Программа находится в конце раздела 11.2.1 и представляет собой простую программу, которая просто считывает некоторые числа из файла и записывает их в обратном порядке.

Соответствующий код в основном методе (данные — это считыватель, а результат — писатель):

try {
    // Read numbers from the input file, adding them to the ArrayList.        
    while ( data.eof() == false ) {  // Read until end-of-file.
        double inputNumber = data.getlnDouble();
        numbers.add( inputNumber );
    }

    // Output the numbers in reverse order.        
    for (int i = numbers.size()-1; i >= 0; i--)
        result.println(numbers.get(i));

    System.out.println("Done!");        
} catch (IOException e) {
    // Some problem reading the data from the input file.
    System.out.println("Input Error: " + e.getMessage());
} finally {
    // Finish by closing the files, whatever else may have happened.
    data.close();
    result.close();
}

Поэтому мне было интересно, почему предложение finally было полезно в этом случае, когда нет других точек выхода из предложений try или catch. Разве методы close не могут быть просто в теле main?

Я подумал, может быть, это потому, что теоретически может быть какое-то другое исключение RuntimeException, которое может привести к сбою программы, а затем оставить Reader & Writers незакрытыми, но тогда не закроет ли их все равно тот факт, что программа потерпела крах?


person mallardz    schedule 24.03.2014    source источник
comment
Возможно и теоретически, но это не будет гарантировано выполняться, несмотря ни на что.   -  person Elliott Frisch    schedule 24.03.2014
comment
не смотря на то, что немного завышен ;)   -  person Guillaume    schedule 24.03.2014
comment
В реальной жизни программный код со временем эволюционирует. Предположим, вы доказали (задача, которая также требует некоторых ресурсов!) в данный момент, что нет других типов исключений, которые могут быть выброшены, и, следовательно, нет других точек выхода. Тем не менее придет день, когда вы что-то измените, и код начнет вести себя по-другому. Поэтому разумно добавить некоторую структуру, чтобы не попасть в ловушку. Это как раз то, о чем все хорошие практики. Единственным исключением является случай, когда это быстродействующая одноразовая утилита без будущего.   -  person ach    schedule 24.03.2014
comment
может случиться так, что исключение в блоке try не обязательно приведет к его сбою, потому что исключение могло быть перехвачено в его собственном предложении catch или где-то еще (вызывающий метод мог также обернуть его в try-catch). дело в том, что вы не можете полагаться на сбой программы и очистку ресурсов за вас.   -  person josephus    schedule 25.03.2014
comment
возможный дубликат Закрытие потоков/сокетов и try-catch-finally   -  person Raedwald    schedule 25.03.2014
comment
Возможный дубликат stackoverflow.com/questions/12035688/   -  person Raedwald    schedule 25.03.2014


Ответы (9)


Ваша идея верна: блок finally закроет ресурсы, даже если возникнет непредвиденное исключение.

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

Обратите внимание, что по-прежнему может скрываться ошибка: если data.close() выдает исключение, result.close() никогда не будет вызвана.

В зависимости от вашей среды существуют различные способы исправления ошибки.

  • в java 7 ff вы можете использовать try-with-resources

  • если вы используете Spring, может быть подходящий шаблон, похожий на Шаблон Jdbc

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

  • концептуально более чистым, но довольно многословным в java pre 8 является реализация шаблона ссуды. Если вы не работаете с разработчиками scala/clojure/haskell, это может быть более запутанным, чем что-либо еще.

person Jens Schauder    schedule 24.03.2014
comment
Каков наилучший способ исправить ошибку, о которой вы упомянули? Вы предлагаете попробовать ..finally в блоке finally? - person Cruncher; 24.03.2014
comment
@Cruncher Обычно я пишу метод closeQuietly(Closeable), который перехватывает и регистрирует любые возникающие исключения. - person Nick Holt; 24.03.2014
comment
@NickHolt Какое удивительное совпадение, но я думаю мой немного более общий. - person Elliott Frisch; 24.03.2014
comment
Кроме того, программа может иметь несколько потоков, и сбой одного из них с исключением не приводит к завершению программы. - person Paŭlo Ebermann; 24.03.2014

Это по очень простой причине: это самый безопасный способ, до Java 7 и try-with-resources, гарантировать, что ваши ресурсы будут закрыты, даже если возникнет исключение.

Подумайте, что произойдет, если вы сделаете вместо этого:

try {
    // some code, then

    resource.close();
} catch (SomeException e) {
    // etc
}

Если ваш SomeException сгенерирован до закрытия ресурса, вы можете получить утечку ресурсов. С другой стороны, помещение resource.close() в finally гарантирует, что оно будет закрыто независимо от того, что еще произойдет.

С Java 7 вы бы использовали это:

try (
    final InputStream in = Files.newInputStream(Paths.get("somefile"));
    // Others
) {
    // work with "in" and others
} catch (Whatever e) {
}

Тогда ваши ресурсы будут закрыты до catch.


В качестве примечания: при использовании Java 6 самый безопасный способ закрыть ваши ресурсы — использовать Closer.

person fge    schedule 24.03.2014
comment
Было бы неплохо заменить комментарий // initialize resources некоторыми реальными ресурсами, чтобы показать, как выполняется распределение переменных. - person Nick Holt; 24.03.2014
comment
Но какая разница, если вы поместите resource.close() после блоков try/catch целиком? Это разница, о которой интересуется спрашивающий. - person doppelgreener; 25.03.2014

Если после этого программа завершится, тогда да, это также закроет ресурсы ввода-вывода.

Но многие программы не завершаются. Есть некоторые, которые должны работать 24/7 в течение многих лет. Поэтому правильная очистка ваших ресурсов является обязательной.

К сожалению, Java ‹ 7 предлагает только механизм автоматической очистки памяти (сборка мусора). В Java 7 вы получаете новый «оператор try-with-resource", который пытается заткнуть дыру.

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

Тем не менее, приведенный выше код по-прежнему содержит ошибки: close() может сам генерировать исключение, поэтому некоторые ресурсы ввода-вывода могут остаться. Вам следует использовать такие инструменты, как IOUtils.closeQuietly() вместо этого:

Reader reader = null;
try {
    reader = ...open...

    ...use reader...
} finally {
    IOUtils.closeQuietly(reader);
}
person Aaron Digulla    schedule 24.03.2014

Из документа Java:

Блок finally всегда выполняется при выходе из блока try. Это гарантирует, что блок finally будет выполнен даже в случае возникновения неожиданного исключения. Но finally полезен не только для обработки исключений — он позволяет программисту избежать случайного обхода кода очистки с помощью return, continue или break. Помещение кода очистки в блок finally всегда является хорошей практикой, даже если исключений не ожидается.

И о ваших опасениях по поводу:

тот факт, что программа потерпела крах, все равно закройте их.

Ресурсы выделяются на уровне OS, а не в вашей программе, поэтому, если у вашей программы нет возможности очиститься, ресурсы будут выделены без реального использования.

person Salah    schedule 24.03.2014

Истинный. Если произойдет RuntimeException, то finally будет выполнен, что приведет к закрытию ресурсов, а это не теоретически. Это практический сценарий.

Далее, даже если происходит IOException (или многие другие пойманные вами). Предложение finally не позволяет вам писать один и тот же код для закрытия много раз.

person codingenious    schedule 24.03.2014

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

finally {
    if (data != null) try { data.close() } catch(exception ex) { ex.printstacktrace() }
}
person Andreas    schedule 24.03.2014

Это на самом деле правильная концепция, которую вы сформировали. Когда происходит CheckedException подобное IOException, все ресурсы должны быть закрыты. Это потому что:

  1. Когда вы выполняли программу, осуществлялся доступ к ресурсам. Предположим, вы изменили несколько вещей и не сохранили их, вы не получите обновленный файл. (Это происходит, когда вы используете буферы — они не записывают данные мгновенно, они записывают фрагментами).

  2. Поскольку файл был открыт в JVM, есть вероятность, что он останется открытым, и вам нужно будет закрыть используемое вами приложение. Следовательно, вам нужно close() ресурсов, чтобы буферы были сброшены, т. е. изменения были сохранены.

Например:

try {
    BufferedReader br = new BufferredReader (new FileReader("Example.txt"));

    ArrayList<String> lines = new ArrayList<>();
    String line;
    while ( (line = br.readLine()) != null ) {
        lines.add(line);
    }
catch (IOException ie) {
    //Error handling.
} finally {
    br.close();
}

РЕДАКТИРОВАТЬ: с появлением JDK1.7 теперь вы можете использовать try with resources, как показано ниже:

try (BufferedReader br = new BufferredReader (new FileReader("Example.txt"));) {
    ArrayList<String> lines = new ArrayList<>();
    String line;
    while ( (line = br.readLine()) != null ) {
        lines.add(line);
    }
catch (IOException ie) {
    //Error handling.
}

Теперь блок finally не нужен, поскольку BufferedReader реализует AutoCloseable. Как следует из названия, он автоматически закрывает буфер, когда остается try (..) блока.

person Hungry Blue Dev    schedule 24.03.2014
comment
IOException — это не RuntimeException, а проверенное исключение. - person Paŭlo Ebermann; 24.03.2014

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

person Engineer2021    schedule 24.03.2014

Метод «закрыть» может сделать больше, чем просто уведомить операционную систему о том, что ресурс больше не требуется. Помимо прочего, объекты, которые инкапсулируют различные формы потоков данных, могут сами буферизовать определенный объем данных и передавать их операционной системе либо при закрытии, либо при накоплении определенного объема данных. Иметь объект, который инкапсулирует поток данных журнала, передающих данные в ОС большими блоками, может быть гораздо эффективнее, чем передача событий журнала в ОС по отдельности, но если программа умирает, не "закрывая" файл журнала, информация, которая вероятно, будет очень актуально для любого, кто диагностирует проблему, будет потерян.

person supercat    schedule 24.03.2014