Оператор try with resources
1. Внешние ресурсы
Иногда в процессе работы Java-программа взаимодействует с объектами вне Java-машины. Например, с файлами на диске. Такие объекты принято называть внешними ресурсами. Внутренние ресурсы — это объекты, созданные внутри Java-машины.
Обычно взаимодействие происходит по такой схеме:
Учет ресурсов
Операционная система ведет строгий учет доступных ресурсов, а также контролирует совместный доступ разных программ к ним. Например, если одна программа меняет какой-то файл, другая программа не может изменить (или удалить) этот файл. Это касается не только файлов, но на их примере понятнее всего.
У операционной системы есть функции (API), которые позволяют программе захватить какой-либо ресурс и/или освободить его. Если ресурс занят, с ним может работать только та программа, которая его захватила. Если ресурс свободен, любая программа может захватить его.
Представьте, что у вас в офисе есть общие кружки. Если кто-то взял кружку, другой уже не может взять ее. Но если ей попользовались, помыли и поставили на место, ее снова может брать кто угодно. Ну или места в метро или в маршрутке. Если место свободно — любой может его занять. Если место занято — им распоряжается тот, кто занял.
Захват внешних ресурсов
Каждый раз, когда ваша Java-программа начинает работать с каким-то файлом на диске, Java-машина запрашивает у операционной системы монопольный доступ к нему. Если ресурс свободен, его захватывает Java-машина.
Но после того, как вы закончили работать с файлом, этот ресурс (файл) нужно освободить: уведомить операционную систему, что он вам больше не нужен. Если вы этого не сделаете, ресурс будет продолжать числиться за вашей программой.
Для каждой запущенной программы операционная система ведет список занятых ресурсов. Если ваша программа превысит разрешенный ей лимит ресурсов, новые ресурсы операционная система вам уже не даст.
Хорошая новость в том, что если ваша программа завершилась, все ресурсы автоматически освобождаются (это делает сама операционная система).
Плохая же новость в том, что если вы пишете серверное приложение (а очень много серверных приложений пишутся на Java), ваш сервер должен работать днями, неделями, месяцами без остановки. И если вы в день открываете 100 файлов и не закрываете их, через пару недель ваше приложение исчерпает свой лимит и упадет. Не очень-то похоже на месяцы стабильной работы.
2. Метод close()
Ниже приведем пример программы, которая что-то пишет в файл и закрывает его за собой – освобождает ресурсы операционной системы. Выглядит это примерно так:
| Код | Примечание |
|---|---|
| Путь к файлу. Получаем объект файла: захватываем ресурс. Пишем в файл Закрываем файл — освобождаем ресурс |
Исключения
Вроде все просто. Однако в процессе работы программы могут возникнуть исключения, и внешний ресурс так и не будет освобожден. А это очень плохо.
Ниже написано два эквивалентных примера:
| Длинный код | Код с try-with-resources |
|---|
Правильно освобождаем ресурсы в Java
Неправильное решение №1
Данное решение опасно, потому что если в коде сгенерируется исключение, то stream.close() не будет вызван. Произойдет утечка ресурса (не закроется соединение, не будет освобожден файловый дескриптор и т.д.)
Неправильное решение №2
Попробуем исправить предыдущий код. Используем try-finally :
Теперь close() всегда будет вызываться (ибо finally ): ресурс в любом случае будет освобождён. Вроде всё правильно. Ведь так?
Неправильное решение №3
Попробуем исправить ситуацию. Если stream.close() может затереть «главное» исключение, то давайте просто «проглотим» исключение из close() :
Теперь вроде всё хорошо. Можем идти пить чай.
Как бы не так. Это решение ещё хуже предыдущего. Почему?
Если сравнить это решение с предыдущим, то там мы хотя бы узнаем, что произошло что-то плохое. Здесь же вся информация об ошибке пропадает.
Неидеальное решение
Итак, как же всё-таки правильно выглядит код обработки ресурса?
Правильное решение (Java 7)
Правильное решение (Java 6 с использованием Google Guava)
В Java 6 средствами одной лишь стандартной библиотеки не обойтись. Однако нам на помощь приходит замечательная библиотека Google Guava. В Guava 14.0 появился класс com.google.common.io.Closer ( try-with-resources для бедных), с помощью которого неидеальное решение выше можно заметно упростить:
Решение заметно длиннее, чем в случае Java 7, но всё же намного короче неидеального решения. Вывод будет примерно таким же, как Java 7.
Выводы
Правильно освобождать ресурсы не так просто, как кажется (просто только в Java 7). Всегда уделяйте этому должное внимание. InputStream и OutputStream ( Reader и Writer ) обрабатываются по-разному (по крайней мере в Java 6)!
Русские Блоги
Использование интерфейса AutoCloseable
Сегодня, когда я изучал JDBC, я посмотрел на метод executeQuery () оператора интерфейса и наткнулся на новый интерфейс:
Когда я увидел предложение, обведенное красной ручкой, я тогда не ответил, и понял, что это множественное наследование интерфейса, но последний интерфейс ранее не был виден;
Затем, когда я нашел ответ в Интернете, я внезапно обнаружил использование AutoCloseable. Как видно из названия, это означает, что он автоматически закрывается. Здесь я буду публиковать комментарий об этом. Я чувствую, что получил много пользы. Недавно я начал искать работу после изучения jdbc. ;
Возможность автоматического управления ресурсами (т.е. ARM) специфична для Java 7. Многим друзьям, которые в настоящее время используют Java 6, необходимо перекомпилировать, чтобы удовлетворить потребности Java 6 при компиляции. Цель состоит в том, чтобы легко обрабатывать внешние ресурсы при обнаружении ошибок или успешном выполнении блоков кода.
Отличительной особенностью Project Coin является то, что он имеет возможность автоматического управления ресурсами (то есть ARM). Эта возможность уникальна для Java 7. Многим друзьям, которые в настоящее время используют Java 6, требуется повторная компиляция при компиляции. Компилировать в соответствии с потребностями Java 6. Цель состоит в том, чтобы легко обрабатывать внешние ресурсы при обнаружении ошибок или успешном выполнении блоков кода. Его оригинальная реализация находится в Open JDK. Рассмотрим следующую утомительную операцию копирования файла, код взят из учебного курса Java Bytestream: [code] FileInputStream in = null;
FileOutputStream out = null;
in = new FileInputStream(«xanadu.txt»);
out = new FileOutputStream(«outagain.txt»);
> [/ code] Выше приведен не только пример кода, но и документация InputStream.close () указывает на то, что он вызовет IOException (аналогичное исключение существует в OutputStream, независимо от случая Чтобы успешно скомпилировать этот код, либо добавьте блок catch наружу, либо продолжайте выбрасывать исключения).
Семантическая область действия блока try-catch-finally также требует, чтобы переменные FileInputStream в и FileOutputStream out были объявлены вне блока (если они определены в блоке try, блок catch и блок finally не доступны ).
Чтобы уменьшить приведенный выше пример кода и уменьшить объем ресурсов, используемых в блоке, язык Java добавляет некоторый контент в блок try. Первоначальная спецификация блока try-with-resources (или блока ARM) была реализована, и позже эта спецификация была включена в сборку 105 JDK 7.
Новый интерфейс java.lang.AutoCloseable был добавлен в API предложения и определяет только метод close (), который будет генерировать исключение. Этот интерфейс является родительским интерфейсом java.io.Closeable, что означает, что все InputStream и OutputStream автоматически получат преимущества, которые дает это поведение. Кроме того, FileLock и ImageInputStream также реализуют интерфейс AutoCloseable.
Таким образом, приведенный выше код можно записать так: [code] try (
FileInputStream in = new FileInputStream(«xanadu.txt»);
FileOutputStream out = new FileOutputStream(«outagain.txt»)
> [/ code] В конце блока try, независимо от того, нормально ли он заканчивается или генерируется исключение, ресурсы out и in автоматически вызывают метод close (). Кроме того, в отличие от предыдущего примера, out.close () и in.close () гарантированно будут выполняться (в предыдущем примере, когда метод in.close () выдает исключение, последующий метод out.close () будет Нет шансов выполнить).
В этом методе есть некоторые тонкости, которые заслуживают нашего внимания:
◆ Как показано в приведенном выше коде, в разделе ресурсов точки с запятой не допускаются после последнего ресурса.
◆ Блок ресурсов отделен от существующего блока try с помощью () вместо общего <>. Если ресурсный блок существует, он должен содержать один или несколько операторов определения ресурса.
◆ Каждое определение ресурса имеет следующую форму: тип var = выражение; обычные операторы нельзя использовать в блоках ресурсов.
◆ Ресурсы неявно являются окончательными, что означает, что эти ресурсы являются окончательными, даже если final не используется. Вы получите ошибку во время компиляции, если попытаетесь присвоить значение переменной ресурса.
◆ Ресурс должен быть подтипом AutoCloseable, в противном случае вы получите ошибку во время компиляции.
◆ Порядок, в котором ресурсы закрыты, является обратным порядку, в котором они определены. Другими словами, в приведенном выше примере кода out.close () вызывается перед in.close (). Это позволяет вам построить вложенный поток и затем закрыть поток снаружи внутрь, что лучше, чем последовательное закрытие (то есть вы можете очистить кэш до закрытия основного потока).
◆ Информация трассировки стека исключений может иметь префикс «Подавлено»: в этих случаях сериализованный формат Throwable также отличается (если клиент Java 6 вызывает службу в удаленной среде выполнения Java 7). Эта проблема возникает и наоборот).
◆ javax.swing и java.sql в настоящее время не добавляются в ARM, классы должны наследовать AutoCloseable для использования в ARM. Если JDBC 4.1 может быть частью JDK 7, он также будет поддерживать ARM, но точное время не было определено.
В jdk1.7 представлен интерфейс AutoCloseable, который автоматически закрывает ресурсы. Некоторые ресурсы также реализуют этот интерфейс, например подготовленные состояния, подключения, InputStream, outputStream и так далее. Когда вы используете его, вам нужно только заключить ресурсы в скобки в блоке try.
Одним из основных нововведений, которые были включены в Java 7, стало введение нового оператора «try c ресурсами». «try c ресурсами» это оператор try, в котором объявляются один или несколько ресурсов. Ресурс — это объект, который должен быть закрыт после того, как программа закончит с ним работу. «try c ресурсами» берет всю работу по закрытию ресурсов на себя. Прежде, чем подробно его рассмотреть давайте разберемся в причинах, которые вызвали его появление.
Закрытие ресурсов до Java 7.
Предположим нам нужно написать метод, который считывает первую строчку из одного файла и записывает ее в другой файл. Задача очень простая и ее реализация не должна вызвать каких-либо затруднений:
Слишком много кода для столь простой операции, не находите? Но и это еще не все, в коде выше есть существенный изъян, если при закрытии первого ресурса:
будет сгенерировано исключение, то второй ресурс закрыт не будет:
Самым очевидным выходом из данной ситуации – будет окружить закрытие ресурсов дополнительными блоками try:
Согласитесь, что выглядит ужасно, но до выхода Java 7 с этим приходилось мириться.
Использование try с ресурсами.
Появление оператора try с ресурсами значительно упростило жизнь программистам, больше не было необходимости громоздить конструкции try/catch в надежде, что ты закрыл все ресурсы и предусмотрел все возможные исключительные ситуации, большую часть этой работы try с ресурсами взял на себя. Рассмотрим его общий вид:
Главное отличие от привычного блока try в круглых скобках, в которых создаются ресурсы, которые впоследствии нужно закрыть, ресурсы будут закрываться снизу-вверх автоматически после завершения работы блока try, т.е. в примере, сначала закроется writer, а потом reader. Ресурсы созданные в блоке try(), в нашем случае reader и writer будут видны только в блоке try, в блоках catch и finally попытка их использования вызовет ошибку компиляции.
При использовании оператора try с ресурсами, совершенно не обязательно использовать блоки catch и finally, они являются опциональными, исходя из этого предыдущий пример можно переписать следующим образом:
Подобный код абсолютно легален и никаких ошибок компиляции не вызовет.
Настало время улучшить наш пример из начала статьи, который копирует первую строчку из одного файла в другой:
Получилось в два раза меньше кода, чем в первоначальном примере.
Интерфейс AutoCloseable.
Для того, чтобы класс можно было использовать в операторе try с ресурсами, он должен реализовывать интерфейс AutoCloseable. Хорошая новость, в том, что сделать это не так уж и сложно – необходимо реализовать всего лишь один метод – public void close() throws Exception.
Как и следовало ожидать сначала выполнился код из блока try, а потом произошло закрытие ресурса, с помощью переопределенного метода close(). Вы наверное заметили, что в примере, метод close() не генерирует и не объявляет никаких исключений, в отличии от метода объявленного в интерфейсе AutoCloseable – это вполне легально, переопределяемый метод может генерировать более конкретные исключения или не генерировать их вовсе. Еще на что хотелось бы обратить пристальное внимание, так это на саму реализацию метода close(). Метод close() не должен содержать никакой бизнес логики, только закрытие ресурсов:
Данный код конечно же скомпилируется, но закрытие ресурсов будет вызывать побочный эффект и изменять значение переменной, чего конечно же нужно избегать.
Подавленные исключения.
Подавленные исключения (suppressed exception) образуются, когда в блоке try генерируется исключение и в методе close() при закрытии ресурса генерируется исключение, в этом случае первое исключение считается главным остальные подавленные, чтобы было понятнее рассмотрим пример:
Вывод:
Main exception
Suppressed Exception
Suppressed Exception
В нашем случае получился следующий ход выполнения программы: сначала создаются два объекта Example ex1 и ex2, после чего происходит вход в блок try, где генерируется исключение, программа прекращает выполнения кода в блоке и приступает к закрытию ресурсов снизу-вверх. При закрытии каждого из ресурсов в методе close() генерируется еще по одному исключению, которые последовательно добавляются к главному исключению, сгенерированному в блоке try. После того, как ресурсы были закрыты, программа приступает к обработке исключения в блоке catch, где первой строчкой мы выводим информацию о главном исключении, а последующим циклом получаем подавленные исключения и выводим информацию о них.
Последнее на что стоит обратить внимание, так это, что подавленные исключения работают только в блоке try, в следующем примере будет сгенерировано два самостоятельных и независимых друг от друга исключения:





