Я пишу приложение, которые соединяются с веб-сайтом и читают одну строку из нее. Я делаю это как это:
try{
URLConnection connection = new URL("www.example.com").openConnection();
BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String response = rd.readLine();
rd.close();
}catch (Exception e) {
//exception handling
}
Действительно ли это хорошо? Я имею в виду, я закрываю BufferedReader в последней строке, но я не закрываю InputStreamReader. Я должен создать автономный InputStreamReader из connection.getInputStream и BufferedReader от автономного InputStreamReader, чем завершение все эти два читателя? Я думаю, что будет лучше поместить заключительные методы в наконец блок как это:
InputStreamReader isr = null;
BufferedReader br = null;
try{
URLConnection connection = new URL("www.example.com").openConnection();
isr = new InputStreamReader(connection.getInputStream());
br = new BufferedReader(isr);
String response = br.readLine();
}catch (Exception e) {
//exception handling
}finally{
br.close();
isr.close();
}
Но это ужасно, потому что заключительные методы могут выдать исключение, таким образом, я должен обработать или бросить его.
Какое решение лучше? Или каково было бы лучшее решение?
Общая идиома для получения и освобождения ресурсов в Java такова:
final Resource resource = acquire();
try {
use(resource);
} finally {
resource.release();
}
Примечание:
try
должен следовать непосредственно за acquire. Это означает, что вы не можете обернуть его в декоратор и сохранить безопасность (и удаление пробелов или размещение всего в одной строке не помогает:). finally
, иначе это не будет безопасно для исключений. null
, используйте final
. В противном случае у вас будет грязный код и вероятность NPE. try
(здесь Java ведет вас в заблуждение). Вы можете абстрагироваться от этой чепухи с помощью идиомы Execute Around, так что вам не придется повторяться (просто напишите много шаблонов).
Это хорошо? То есть я закрываю BufferedReader в последней строке, но не закрываю InputStreamReader.
Помимо того, что это должно быть сделано в finally
(чтобы гарантировать закрытие даже в случае исключения), это нормально. Классы ввода-вывода Java используют шаблон декоратора. Закрытие будет делегировано базовым потокам.
Но это некрасиво, потому что методы закрытия могут вызывать исключение, поэтому я должен обработать или выбросить его.
Когда закрытие вызывает исключение, это часто просто означает, что другая сторона была закрыта или удалена, что полностью вне вашего контроля. Вы можете записать на максимум или проигнорировать это. В простом приложении я бы просто проигнорировал это. В критически важном приложении я бы записал это на всякий случай.
Короче говоря, ваш код можно переписать так:
BufferedReader br = null;
try {
URLConnection connection = new URL("www.example.com").openConnection();
br = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String response = br.readLine();
}catch (Exception e) {
//exception handling
}finally{
if (br != null) try { br.close(); } catch (IOException ignore) {}
}
В Java 7 будет автоматическая обработка ресурсов, которая сделает ваш код кратким, как:
try (BufferedReader br = new InputStreamReader(new URL("www.example.com").openStream())) {
String response = br.readLine();
} catch (Exception e) {
//exception handling
}
См. Также:
Я думаю, что будет лучше поместить закрывающие методы в блок finally
Да, всегда. Потому что может произойти исключение и ресурсы не будут освобождены/закрыты должным образом.
Вам нужно закрыть только самый внешний читатель, потому что он будет отвечать за закрытие всех окружающих читателей.
Да, это уродливо... пока что. Я думаю, есть планы по автоматическому управлению ресурсами в Java.
Я бы использовал для этого apache commons IO, как предлагали другие, в основном IOUtils. toString(InputStream) и IOUtils.closeQuietly(InputStream):
public String readFromUrl(final String url) {
InputStream stream = null; // keep this for finally block
try {
stream = new URL(url).openConnection().getInputStream(); // don't keep unused locals
return IOUtils.toString(stream);
} catch (final IOException e) {
// handle IO errors here (probably not like this)
throw new IllegalStateException("Can't read URL " + url, e);
} finally {
// close the stream here, if it's null, it will be ignored
IOUtils.closeQuietly(stream);
}
}
Вам не нужно несколько операторов close для любого из вложенных потоков и читателей в java.io. Очень редко нужно закрывать более одной вещи в одном finally - большинство конструкторов могут выбросить исключение, так что вы будете пытаться закрыть то, что еще не создали.
Если вы хотите закрыть поток независимо от того, удалось ли чтение или нет, то вам нужно поместить его в finally.
Не присваивайте переменным null, а затем сравнивайте их, чтобы узнать, произошло ли что-то раньше; вместо этого структурируйте свою программу так, чтобы путь, на котором вы закрываете поток, мог быть достигнут только в том случае, если исключение не будет выброшено. Кроме переменных, используемых для итерации в циклах for, переменные не должны менять значение - я склонен помечать все конечными, если нет необходимости делать иначе. Наличие флагов в вашей программе, чтобы сообщить вам, как вы пришли к выполняемому в данный момент коду, а затем изменение поведения на основе этих флагов - это в значительной степени процедурный (даже не структурированный) стиль программирования.
То, как вы расположите блоки try/catch/finally, зависит от того, хотите ли вы по-разному обрабатывать исключения, возникающие на разных этапах.
private static final String questionUrl = "http://stackoverflow.com/questions/3044510/";
public static void main ( String...args )
{
try {
final URLConnection connection = new URL ( args.length > 0 ? args[0] : questionUrl ).openConnection();
final BufferedReader br = new BufferedReader ( new InputStreamReader (
connection.getInputStream(), getEncoding ( connection ) ) );
try {
final String response = br.readLine();
System.out.println ( response );
} catch ( IOException e ) {
// exception handling for reading from reader
} finally {
// br is final and cannot be null. no need to check
br.close();
}
} catch ( UnsupportedEncodingException uee ) {
// exception handling for unsupported character encoding
} catch ( IOException e ) {
// exception handling for connecting and opening reader
// or for closing reader
}
}
getEncoding
должен проверить результаты getContentEncoding()
и getContentType()
соединения, чтобы определить кодировку веб-страницы; ваш код просто использует кодировку платформы по умолчанию, которая вполне может быть неверной.
Ваш пример необычен с точки зрения структуры, поскольку он очень процедурный; обычно вы разделяете печать и получение в более крупной системе и позволяете клиентскому коду обрабатывать любые исключения (или иногда перехватывать и создавать собственные исключения):
public static void main ( String...args )
{
final GetOneLine getOneLine = new GetOneLine();
try {
final String value = getOneLine.retrieve ( new URL ( args.length > 0 ? args[0] : questionUrl ) );
System.out.println ( value );
} catch ( IOException e ) {
// exception handling for retrieving one line of text
}
}
public String retrieve ( URL url ) throws IOException
{
final URLConnection connection = url.openConnection();
final InputStream in = connection.getInputStream();
try {
final BufferedReader br = new BufferedReader ( new InputStreamReader (
in, getEncoding ( connection ) ) );
try {
return br.readLine();
} finally {
br.close();
}
} finally {
in.close();
}
}
Как отметил Макдауэлл, вам может понадобиться закрыть входной поток, если new InputStreamReader
бросает.
Достаточно закрыть BufferedReader - это также закрывает базовый читатель.
Ишай опубликовал хороший шаблон для закрытия потоков (закрытие могло вызвать другое исключение).
BufferedReader br = null;
Вы объявляете переменную без ее присвоения ( null
не учитывается - в данном случае это бесполезное присвоение). Это "запах" кода в Java (ссылка Эффективная Java ; Код завершен для получения дополнительной информации об объявлении переменных).
}finally{
br.close();
isr.close();
}
Во-первых, вам нужно только закрыть самый верхний декоратор потока ( br
закроет isr
). Во-вторых, если br.close ()
выдает исключение, isr.close ()
не будет вызван, так что это не звуковой код. При определенных условиях исключения ваш код будет скрывать исходное исключение с помощью NullPointerException
.
isr = new InputStreamReader(connection.getInputStream());
Если (по общему признанию маловероятное) событие, которое конструктор InputStreamReader
вызвало какое-либо исключение времени выполнения, поток из соединения не будет закрыт.
Используйте интерфейс Closeable
, чтобы уменьшить избыточность.
Вот как я бы написал ваш код:
URLConnection connection = new URL("www.example.com").openConnection();
InputStream in = connection.getInputStream();
Closeable resource = in;
try {
InputStreamReader isr = new InputStreamReader(in);
resource = isr;
BufferedReader br = new BufferedReader(isr);
resource = br;
String response = br.readLine();
} finally {
resource.close();
}
Обратите внимание:
Некоторое время назад я потратил некоторое время на размышления о том, как избежать утечки ресурсов / данных, когда что-то пойдет не так .