что такое ресурс в конструкции try with resources
Инструкция try-with-resources в Java
Вступление
Синтаксис
То, как Java понимает этот код:
До внедрения этого подхода закрытие ресурсов выполнялось вручную, как показано в предыдущем коде. По сути, это был стандартный код, и базы кода были завалены ими, что снижало читаемость и затрудняло их обслуживание.
catch и наконец часть попробуйте с ресурсами работают, как ожидалось, с catch блоками, обрабатывающими соответствующие исключения, и наконец блок выполняется независимо от того, было ли исключение или нет. Единственное отличие-это подавленные исключения, которые объясняются в конце этой статьи.
Множество Ресурсов
Еще одним хорошим аспектом попробуйте с ресурсами является простота добавления/удаления ресурсов, которые мы используем, с гарантией того, что они будут закрыты после того, как мы закончим.
Если бы мы хотели работать с несколькими файлами, мы бы открыли файлы в инструкции try() и разделили их точкой с запятой:
Поддерживаемые Классы
Это, конечно, включает в себя определенные пользователем объекты, реализующие Автоклавируемый интерфейс. Однако вы редко столкнетесь с ситуацией, когда захотите написать свои собственные ресурсы.
Обработка исключений
Представьте себе ситуацию:
Важно понимать, что порядок исполнения:
Например, вот ресурс, который ничего не делает, кроме как создает исключения:
Git Essentials
Ознакомьтесь с этим практическим руководством по изучению Git, содержащим лучшие практики и принятые в отрасли стандарты. Прекратите гуглить команды Git и на самом деле изучите это!
Подсказка : Внезапно исключения, возникающие при закрытии ресурсов, становятся важными.
Помните, что внутри блока try может быть создано только одно исключение. Как только возникает исключение, код блока try завершается, и Java пытается закрыть ресурсы.
Вывод
Код более читабелен, его легче изменять и поддерживать, и обычно он короче.
Правильно освобождаем ресурсы в 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)!
Java 7: объяснение с использованием ресурсов
Оператор try-with-resources обеспечивает закрытие каждого ресурса в конце оператора. Любой объект, который реализует интерфейс java.lang.AutoCloseable или java.io.Closeable, может использоваться в качестве ресурса.
Перед попыткой использования ресурсов (до Java 7) при работе с объектами SQL Statement, ResultSet, Connection или другими объектами ввода-вывода необходимо было явно закрыть ресурс. Так что можно написать что-то вроде:
Давайте посмотрим на пример того, как мы будем использовать try..catch… наконец, в предварительной версии Java 7. Позвольте мне создать 2 пользовательских исключения — ExceptionA и ExceptionB. Они будут использованы в примерах.
Давайте создадим некоторый ресурс, скажем OldResource, который имеет два метода — doSomeWork (): который выполняет некоторую работу, и close (): который выполняет закрытие. Обратите внимание, что это описывает использование общего ресурса — сделайте некоторую работу, а затем закройте ресурс. Теперь каждая из этих операций, doSomeWork и close, выдает исключение.
Давайте использовать этот ресурс в примере программы:
Программа проста: создайте новый ресурс, используйте его, а затем попытайтесь закрыть его. Можно посмотреть на количество дополнительных строк кода там.
Код NewResource ниже:
Теперь давайте используем NewResource в примере программы, используя try-with-resource:
Одна вещь, которую следует отметить выше, состоит в том, что Исключение, выбрасываемое при закрытии, подавляется Исключением, которое выдается рабочим методом.
Таким образом, вы можете сразу заметить разницу между двумя реализациями, одна из которых использует try… catch… finally, а другая — try-with-resource. В приведенном выше примере только один ресурс объявлен как используемый. В блоке try можно объявить и использовать несколько ресурсов, а также вложить эти блоки try-with-resources.
Однако в Java 7 выброшенное исключение отслеживает исключения, которые оно подавляло на пути к перехвату / обработке. Таким образом, приведенный выше пример можно переформулировать следующим образом. ExceptionB, генерируемый методом close, добавляется в список исключенных исключений ExceptionA, который генерируется блоком try.
Позвольте мне объяснить вам вложенные попытки с ресурсами и исключенные исключения на следующих примерах.
Что такое ресурс в конструкции try with resources
Начнем с предыстории. Для начала рассмотрим метод finalize() класса Object. Раз данный метод принадлежит классу Object, значит его наследуют все классы и соответственно объекты на базе этих классов. Метод finalize() — это специальный метод, который вызывается у объекта Java-машиной перед тем, как сборщик мусора уничтожит данный объект. Данный метод был придуман для освобождения внешних ресурсов, которые занимал данный объект. Под внешними ресурсами имеются ввиду файлы, потоки ввода-вывода и т. д.
Но есть один большой минус данного метода — Java-машина может отложить уничтожение объекта, как и вызов метода finalize() на сколько угодно. Более того, Java-машина вообще не гарантирует, что данный метод будет вызван. В куче ситуаций ради «оптимизации» метод finalize() не вызывается.
Начиная с 7 версии, в Java появился альтернативный подход к решению данной задачи. Называется данный подход — try-with-resources.
В отличие от метода finalize(), блок finally из конструкции try-catch-finally вызывается всегда. Этим и пользовались программисты, когда нужно было гарантированно освободить ресурсы, закрыть потоки и т.д.
Независимо от того, нормально ли отработал блок try, или там возникло исключение, блок finally вызовется всегда, и там можно будет освободить занятые ресурсы.
Поэтому в Java 7 этот подход решили сделать официальным, и вот что из этого вышло:
Это специальная конструкция try, называемая try-with-resources. После try следуют круглые скобки, где объявляются переменные и создаются объекты. Эти объекты можно использовать внутри блока try, обозначенного скобками <>. Когда выполнение команд блока try закончится, независимо от того – нормально оно закончилось или было исключение, для объекта, созданного внутри круглых скобок (), будет вызван метод close(). Но для того чтобы иметь возможность создать объект внутри круглых скобок блока try необходимо имплементировать свой объект от специального интерфейса AutoCloseable.
Данный интерфейс имеет всего лишь один метод close(), который необходимо переопределить, и именно данный метод будет вызываться. Только объекты такого типа можно использовать внутри круглых скобок try-with-resources для «автоматического закрытия».
При необходимости внутри круглых скобок можно создавать несколько объектов, разделив их точкой с запятой. После последнего объекта точка с запятой не указывается.
9 лучших практик для обработки исключений в Java
Независимо от того, новичок вы или профессионал, всегда полезно освежить в памяти методы обработки исключений, чтобы убедиться, что вы и ваша команда можете справиться с проблемами.
Вот почему у большинства команд разработчиков есть собственный набор правил их использования. И если вы новичок в команде, вас может удивить, насколько эти правила могут отличаться от тех, которые вы использовали раньше.
Тем не менее, есть несколько передовых практик, которые используются большинством команд. Вот 9 самых важных из них, которые помогут вам начать работу или улучшить обработку исключений.
1. Освободите ресурсы в блоке finally или используйте инструкцию «Try-With-Resource»
Довольно часто вы используете ресурс в своем блоке try, например InputStream, который вам нужно закрыть позже. Распространенной ошибкой в таких ситуациях является закрытие ресурса в конце блока try.
Проблема в том, что этот подход работает отлично до тех пор, пока не генерируется исключение. Все операторы в блоке try будут выполнены, и ресурс будет закрыт.
Но вы не зря добавили блок try. Вы вызываете один или несколько методов, которые могут вызвать исключение, или, может быть, вы сами вызываете исключение. Это означает, что вы можете не дойти до конца блока try. И как следствие, вы не закроете ресурсы.
Поэтому вам следует поместить весь код очистки в блок finally или использовать оператор try-with-resource.
Используйте блок Finally
В отличие от последних нескольких строк вашего блока try, блок finally всегда выполняется. Это происходит либо после успешного выполнения блока try, либо после обработки исключения в блоке catch. Благодаря этому вы можете быть уверены, что освободите все захваченные ресурсы.
Оператор Java 7 «Try-With-Resource»
Вы можете использовать его, если ваш ресурс реализует интерфейс AutoCloseable. Это то, что делает большинство стандартных ресурсов Java. Когда вы открываете ресурс в предложении try, он автоматически закрывается после выполнения блока try или обработки исключения.
2. Конкретные исключения предпочтительнее
Чем конкретнее исключение, которое вы генерируете, тем лучше. Всегда помните, что коллеге, который не знает вашего кода, а может быть, и вам через несколько месяцев, необходимо вызвать ваш метод и обработать исключение.
Поэтому постарайтесь предоставить им как можно больше информации. Это упрощает понимание вашего API. В результате вызывающий ваш метод сможет лучше обработать исключение или избежать его с помощью дополнительной проверки.
Поэтому всегда старайтесь найти класс, который лучше всего подходит для вашего исключительного события, например, генерируйте NumberFormatException вместо IllegalArgumentException. И избегайте создания неспецифического исключения.
3. Документируйте определенные вами исключения
Каждый раз, когда вы определяете исключение в сигнатуре вашего метода, вы также должны задокументировать его в своем Javadoc. Это преследует ту же цель, что и предыдущая передовая практика: предоставить вызывающему как можно больше информации, чтобы он мог избежать или обработать исключение.
Итак, не забудьте добавить объявление @throws в свой Javadoc и описать ситуации, которые могут вызвать исключение.
4. Генерирование исключений с описательными сообщениями
Идея, лежащая в основе этой передовой практики, аналогична двум предыдущим. Но на этот раз вы не предоставляете информацию вызывающей стороне вашего метода. Сообщение об исключении читают все, кто должен понимать, что произошло, когда исключение было зарегистрировано в файле журнала или в вашем инструменте мониторинга.
Следовательно, он должен как можно точнее описать проблему и предоставить наиболее актуальную информацию для понимания исключительного события.
Не поймите меня неправильно; вы не должны писать абзац текста. Но вам следует объяснить причину исключения в 1-2 коротких предложениях. Это помогает вашей группе эксплуатации понять серьезность проблемы, а также упрощает анализ любых инцидентов, связанных с обслуживанием.
Если вы выберете конкретное исключение, его имя класса, скорее всего, уже будет описывать тип ошибки. Таким образом, вам не нужно предоставлять много дополнительной информации. Хорошим примером этого является NumberFormatException. Оно вызывается конструктором класса java.lang.Long, когда вы предоставляете String в неправильном формате.
Название класса NumberFormatException уже говорит вам о типе проблемы. Его сообщение должно содержать только строку ввода, которая вызвала проблему. Если имя класса исключения не так выразительно, вам необходимо предоставить необходимую информацию в сообщении.
5. Сначала перехватите наиболее конкретное исключение
Большинство IDE помогут вам в этой лучшей практике. Они сообщают о недостижимом блоке кода, когда вы сначала пытаетесь перехватить менее конкретное исключение.
Проблема в том, что выполняется только первый блок catch, соответствующий исключению. Итак, если вы сначала поймаете IllegalArgumentException, вы никогда не достигнете блока catch, который должен обрабатывать более конкретное NumberFormatException, потому что это подкласс IllegalArgumentException.
Всегда сначала перехватывайте наиболее конкретный класс исключения и добавляйте менее конкретные блоки перехвата в конец вашего списка.
6. Не перехватывайте Throwable
Если вы используете Throwable в предложении catch, он не только перехватит все исключения; он также перехватит все ошибки. JVM выдает ошибки, чтобы указать на серьезные проблемы, которые не предназначены для обработки приложением. Типичными примерами этого являются OutOfMemoryError или StackOverflowError. И то, и другое вызвано ситуациями, которые находятся вне контроля приложения и не могут быть обработаны.
Итак, лучше не перехватывайте Throwable, если вы не абсолютно уверены, что находитесь в исключительной ситуации, в которой вы можете или обязаны обрабатывать ошибку.
7. Не игнорируйте исключения
Вы когда-нибудь анализировали отчет об ошибке, в котором выполнялась только первая часть вашего сценария использования?
Часто это вызвано игнорируемым исключением. Разработчик, вероятно, был уверен, что оно никогда не будет вызвано, и добавил блок catch, который не обрабатывает и не регистрирует его. И когда вы найдете этот блок, вы, скорее всего, даже найдете один из известных комментариев «Этого никогда не будет».
Что ж, возможно, вы анализируете проблему, в которой произошло невозможное.
Поэтому, пожалуйста, никогда не игнорируйте исключения. Вы не знаете, как код изменится в будущем. Кто-то может удалить проверку, которая предотвратила исключительное событие, не осознавая, что это создает проблему. Или код, который генерирует исключение, изменяется и теперь генерирует несколько исключений одного и того же класса, а вызывающий код не предотвращает их все.
Вы должны хотя бы написать сообщение в журнале, сообщающее всем, что произошло немыслимое и что кто-то должен это проверить.
8. Не пишите в лог сгенерированные исключения
Это, вероятно, наиболее часто игнорируемая передовая практика в списке. Вы можете найти множество фрагментов кода и даже библиотек, в которых исключение перехватывается, регистрируется и повторно генерируется.
Может показаться интуитивно понятным регистрировать исключение, когда оно произошло, а затем повторно генерировать его, чтобы вызывающий мог обработать его соответствующим образом. Но, в таком случае, приложение будет писать в лог несколько сообщений об ошибках для одного и того же исключения.
Повторные сообщения также не добавляют никакой информации. Как объясняется в лучшей практике №4, сообщение об исключении должно описывать исключительное событие. А трассировка стека сообщает вам, в каком классе, методе и строке было сгенерировано исключение.
Если вам нужно добавить дополнительную информацию, вы должны перехватить исключение и обернуть его в пользовательское. Но обязательно следуйте передовой практике номер 9.
Итак, перехватывайте исключение, только если вы хотите его обработать. В противном случае укажите это в сигнатуре метода и позвольте вызывающей стороне позаботиться об этом.
9. Оберните исключение, не обрабатывая его
Иногда лучше поймать стандартное исключение и превратить его в настраиваемое. Типичным примером такого исключения является бизнес-исключение для конкретного приложения или платформы. Это позволяет вам добавлять дополнительную информацию, а также вы можете реализовать специальную обработку для вашего класса исключения.
Когда вы это сделаете, обязательно установите исходное исключение в качестве причины. Класс Exception предоставляет определенные методы конструктора, которые принимают Throwable в качестве параметра. В противном случае вы потеряете трассировку стека и сообщение об исходном исключении, что затруднит анализ исключительного события, вызвавшего ваше исключение.
Резюме
Как вы видели, есть много разных вещей, которые вы должны учитывать, когда генерируете или перехватываете исключение. Большинство из них имеют цель улучшить читаемость вашего кода или удобство использования вашего API.
Чаще всего исключения являются одновременно механизмом обработки ошибок и средством связи. Поэтому вам следует обязательно обсудить передовые практики и правила, которые вы хотите применять, со своими коллегами, чтобы все понимали общие концепции и использовали их одинаково.