html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Правильно освобождаем ресурсы в Java

Неправильное освобождение ресурсов — одна из наиболее часто допускаемых ошибок среди Java-программистов. Под ресурсом в данной статье я буду подразумевать всё, что реализует интерфейс java.io.Closeable. Итак, сразу к делу.

Будем рассматривать на примере OutputStream. Задача: получить на вход OutputStream, сделать некоторую полезную работу с ним, закрыть OutputStream.

Неправильное решение №1


OutputStream stream = openOutputStream();
// что-то делаем со stream
stream.close();


Данное решение опасно, потому что если в коде сгенерируется исключение, то stream.close() не будет вызван. Произойдет утечка ресурса (не закроется соединение, не будет освобожден файловый дескриптор и т.д.)

Неправильное решение №2


Попробуем исправить предыдущий код. Используем try-finally:

OutputStream stream = openOutputStream();
try {
   // что-то делаем со stream
} finally {
   stream.close();
}


Теперь close() всегда будет вызываться (ибо finally): ресурс в любом случае будет освобождён. Вроде всё правильно. Ведь так?

Нет.

Проблема следующая. Метод close() может сгенерировать исключение. И если при этом основной код работы с ресурсом тоже выбросит исключение, то оно перезатрется исключением из close(). Информация об исходной ошибке пропадёт: мы никогда не узнаем, что было причиной исходного исключения.

Неправильное решение №3


Попробуем исправить ситуацию. Если stream.close() может затереть «главное» исключение, то давайте просто «проглотим» исключение из close():

OutputStream stream = openOutputStream();
try {
   // что-то делаем со stream
} finally {
   try {
      stream.close();
   } catch (Throwable unused) {
      // игнорируем
   }
}


Теперь вроде всё хорошо. Можем идти пить чай.

Как бы не так. Это решение ещё хуже предыдущего. Почему?

Потому что мы просто взяли и проглотили исключение из close(). Допустим, что outputStream — это FileOutputStream, обёрнутый в BufferedOutputStream. Так как BufferedOutputStream делает flush() на низлежащий поток порциями, то есть вероятность, что он его вызовет во время вызова close(). Теперь представим, что файл, в который мы пишем, заблокирован. Тогда метод close() выбросит IOException, которое будет успешно «съедено». Ни одного байта пользовательских данных не записались в файл, и мы ничего об этом не узнали. Информация утеряна.

Если сравнить это решение с предыдущим, то там мы хотя бы узнаем, что произошло что-то плохое. Здесь же вся информация об ошибке пропадает.

Замечание: если вместо OutputStream используется InputStream, то такой код имеет право на жизнь. Дело в том, что если в InputStream.close() выбрасывается исключение, то (скорее всего) никаких плохих последствий не будет, так как мы уже считали с этого потока всё что хотели. Это означает, что InputStream и OutputStream имеют совершенно разную семантику.

Неидеальное решение


Итак, как же всё-таки правильно выглядит код обработки ресурса?

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

OutputStream stream = openOutputStream();
Throwable mainThrowable = null;

try {
    // что-то делаем со stream
} catch (Throwable t) {
    // сохраняем исключение
    mainThrowable = t;
    // и тут же выбрасываем его
    throw t;
} finally {
     if (mainThrowable == null) {
         // основного исключения не было. Просто вызываем close()
         stream.close();
     }
     else {
         try {
            stream.close();
         } catch (Throwable unused) {
             // игнорируем, так как есть основное исключение
             // можно добавить лог исключения (по желанию)
         }
     }
}




Минусы такого решения очевидны: громоздко и сложно. Кроме того, пропадает информация об исключении из close(), если основной код выбрасывает исключение. Также openOutputStream() может вернуть null, и тогда вылетит NullPointerException (решается добавлением еще одного if'а, что приводит к ещё более громоздкому коду). Наконец, если у нас будет два ресурса (например, InputStream и OutputStream) и более, то код просто будет невыносимо сложным.

Правильное решение (Java 7)


В Java 7 появилась конструкция try-with-resources. Используем её:

try (OutputStream stream = openOutputStream()) {
    // что-то делаем со stream
}


И всё.

Если исключение будет выброшено в основном коде и в методе close(), то приоритетнее будет первое исключение, а второе исключение будет подавлено, но информация о нем сохранится (с помощью метода Throwable.addSuppressed(Throwable exception), который вызывается неявно Java компилятором):

Exception in thread "main" java.lang.RuntimeException: Main exception
	at A$1.write(A.java:16)
	at A.doSomething(A.java:27)
	at A.main(A.java:8)
	Suppressed: java.lang.RuntimeException: Exception on close()
		at A$1.close(A.java:21)
		at A.main(A.java:9)


Правильное решение (Java 6 с использованием Google Guava)


В Java 6 средствами одной лишь стандартной библиотеки не обойтись. Однако нам на помощь приходит замечательная библиотека Google Guava. В Guava 14.0 появился класс com.google.common.io.Closer (try-with-resources для бедных), с помощью которого неидеальное решение выше можно заметно упростить:

Closer closer = Closer.create();
try {
   OutputStream stream = closer.register(openOutputStream());
   // что-то делаем со stream
} catch (Throwable e) { // ловим абсолютно все исключения (и даже Error'ы)
   throw closer.rethrow(e);
} finally {
   closer.close();
}


Решение заметно длиннее, чем в случае Java 7, но всё же намного короче неидеального решения. Вывод будет примерно таким же, как Java 7.

Closer также поддерживает произвольное количество ресурсов в нём (метод register(...)). К сожалению, Closer — это класс, помеченный аннотацией @Beta, а значит может подвергнуться значительным изменениям в будущих версиях библиотеки (вплоть до удаления).

Выводы


Правильно освобождать ресурсы не так просто, как кажется (просто только в Java 7). Всегда уделяйте этому должное внимание. InputStream и OutputStream (Reader и Writer) обрабатываются по-разному (по крайней мере в Java 6)!

Дополнения/исправления приветствуются!

В следующий раз я планирую рассказать, как бороться с NullPointerException.
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

2

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции