Правильно освобождаем ресурсы в 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.
    Поделиться публикацией
    Похожие публикации
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 82
    • +8
      Это просто статья о try-with-resources что ли?
      • +5
        try-with-resources появился только в Java 7. Куча проектов до сих пор сидит на Java 6 и ниже
        • –2
          Кто мешает перейти на 7? Java же делает упор на обратную совместимость. Чесно не могу представить себе Java 6 код, который не скомпилится (хоть и warning) в Java 7.
          • +3
            Попробуйте это объяснить заказчику. Он не будет рисковать переходом на Java 7 на своих production серверах только ради каких-то там синтаксических плюшек.
            • 0
              Могут быть и ошибки компиляции — в java 7 исправлены баги влияющие на видимость некоторых полей класса. Но это исправляется легко. Другое дело, если в коде есть криво написанные equals и compareTo методы, то работать в java 7 они могут иначе. Поэтому опасения вполне обоснованные. Подобный переход необходимо тщательно тестировать.
              • 0
                А ещё есть мир Android… Уговорите Гугл сделать поддержку классов, сгенерированных 7-й явой, в своём dx-компиляторе.
                • +1
                  С компиляцией ошибки — легко. В Java 7 изменились некоторые интерфейсы. Например, java.sql.Connection. Если в вашем проекте этот интерфейс был реализован, вы получите ошибку компиляции.

                  Ещё в деплой могут быть встроены тулзы для автоматического анализа и преобразования кода (скажем, обфускация), которые могут не поддерживать новые исходники или байткод.
                  • +2
                    Ну я надеюсь вы поняли, что сказали глупость. Нереально иногда просто поднять какой нибудь Tapestry до версии хотя бы 4.1 не то что 1.7 java внедрить.
                    • +1
                      Обратная совместимость, говорите?
                      Если через X-сервер запустить свинговое приложение на 7 JRE, то после потери фокуса ввода, вы никак не сможете этот фокус ввода вернуть обратно. Из работающих решений — только запуск под более ранней версией JRE. Баг до сих пор не закрыт.
                      Погуглите «swing application lost focus on x-server».
                      • 0
                        Про совместимость уже понял.

                        А вот баг про который вы говорите, я с ним даже столкнулся! В продакшене на Debian 6 и последнем JRE, фокус не востанавливается в модальных диалоговых окнах. На тестовом никак не мог воспроизвести, хотя была таже Debian с чуть боле новыми пакетами. Баг в принципе был не критичен, я и забил, но вашу мысль про совместимость теперь понял точно.
                        • +1
                          Мало того, в пределах даже одной версии Java нету абсолютной обратной совместимости. Например, в апдейте 6u21 System.getProperty(«java.vendor») вместо Sun Microsystems Inc. стал возвращать Oracle Corporation. Кто-то мог на это завязаться, в итоге некоторые приложения переставали работать правильно.
                          • 0
                            это не баг, а фича, связанная с продажей.
                    • +2
                      Была бы там идиома RAII, или хотя бы гарантия, что close не будет вызывать исключения… Но действительно, почему не сделать так, чтобы освобождение ресурсов гарантированно исполнялось?
                      • 0
                        Если честно, не понял, что вы хотели сказать. Можете каждое предложение пояснить?
                        • 0
                          Для начала посмотрите как в С++ вообще обходятся без блока finally. Держится это на двух вещах.
                          Во первых для объектов, размещенных на стеке, в момент выхода за область видимости будет вызван деструктор, где будет размещено освобождение ресурсов. Во вторых существует соглашение благодаря которому деструкторы ни при каких обстоятельствах не кидают исключений.
                          В случае с Java деструкторов нет как таковых, но вот потребовать гарантий, что освобождение ресурсов не будет кидать исключений, мы можем. Тогда второй вариант станет, внезапно, правильным и самым лаконичным.
                          • +1
                            А как мы потребуем, что освобождение не будет кидать исключений? Взять тот же пример BufferedOutputStream(FileOutputStream). Там метод close() по сути сбрасывает информацию на диск. Если диск в момент вызова close() сдох?
                            • 0
                              А какой смысл в структурной обработке исключений в таких случаях?
                              • 0
                                Вместо экстремального «сдох винт» там могло бы быть «кончилось место»
                                • +2
                                  А чем этот случай отличается от других? Почему в данном случае «разрешено» не обрабатывать исключения?
                                • 0
                                  За сброс отвечает функция flush(), close() же просто освобождает ресурс и ничего не гарантирует кроме освобождения ресурса. Так сделано везде. Кроме явы. И проведение в яве — идиотизм.
                                  • 0
                                    Может и идиотизм, но сдаётся мне, что если бы было так, как вы сказали, то Java-программы содержали бы ещё больше ошибок. Потому что многие бы забывали про вызов ещё плюс одной функции. Это как с checked исключениями. Идея благая, но на деле мало кто умеет правильно обрабатывать исключения. По этой причине от checked исключений отказались в C# и Scala.
                                    • –3
                                      Что за бред вы пишите? Еще раз повторяю — описанное мною поведение стандартно и привычно. Я вот до вчерашнего дня даже не подозревал, что флашить в яве необязательно.
                                      • 0
                                        Стандартно? В C# StreamWriter.Dispose() тоже делает Flush(). Я уже назвал две платформы (JVM и CLR), где это не так. Можете подтвердить свои слова про «стандартно и привычно»?
                                        • –2
                                          В C# хотя бы using есть. Там не нужно было ждать 16 лет, пока его добавят.
                                          • –2
                                            Стандартная функция fclose() в любой операционной системе закрывает дескриптор файла и не гарантирует успешность флаша. Это написано в любой документации, хоть к с++ хоть к паскалю.
                                            • 0
                                              В C++ есть std::ofstream, в котором flush() необязательно делать. Ещё раз, о какой стандартноcти и привычности вы говорите?
                                              • +1
                                                close() же просто освобождает ресурс и ничего не гарантирует кроме освобождения ресурса. Так сделано везде.
                                                Нет. Везде close() или (его синоним) гарантированно вызывает flush().
                                                • 0
                                                  Но он не гарантирует успешность, гарантия только на закрытие файла.
                                                  • +1
                                                    Нет. Весь файловый IO не гарантирует успешность.
                                    • 0
                                      В случае с Java деструкторов нет как таковых
                                      Справедливости ради стоит отметить, что в Java есть финализаторы, которые вызываются когда объект собирается сборщиком мусора.

                                      потребовать гарантий, что освобождение ресурсов не будет кидать исключений, мы можем
                                      Метод close() у некоторых объектов (например буфферизированный output stream) не только освобождает ресурсы, а и может производить запись (и это не зависит от языка программирования, в С++ ofstream::close() тоже сбрасывает буфер). Как Вы предлагаете проверять на ошибку в таком случае? Как в C++? Делать мануально close() в области видимости (т.е. до вызова деструктора) и проверять флаг failbit в iostate (или ловить исключение)? Разве это будет «лаконично»?
                                      • +1
                                        В Java финализаторы такие, что лучше и не знать об их существовании…
                                        • –1
                                          Запись производит flush(), я уже чуть выше отписал.
                                    • +2
                                      Но действительно, почему не сделать так, чтобы освобождение ресурсов гарантированно исполнялось?
                                      Потому, что это невозможно в общем случае.
                                      • 0
                                        Да потому что RAII добавит работы сборщику мусора и «сломает» существующие алгоритмы. При выходе из области видимости сборщик мусора должен запустится, что не проблема для простого сборщика как в Python или Ruby, уменьшили счетчик ссылок при достижении нуля удаляем объекты, но проблема для Java/.NET потому что запуск сборщика означает «замри мир».
                                        • +1
                                          >>Да потому что RAII добавит работы сборщику мусора и «сломает» существующие алгоритмы. При выходе из области видимости сборщик мусора должен запустится

                                          Зачем запускаться сборщику мусора ?? Нужно просто выполнить деструктор и всё.
                                      • +1
                                        Код с гуавой плох тем, что глотает checked exception
                                        То есть если " // что-то делаем со stream" кидает CheckedUserException, который я объявил в сигнатуре функции, то гуава обернет его во что-то
                                        Для этого там конечно есть еще два rethrow, так что до двух CheckedException еще можно выкинуть… Ну вобщем стремно это все.
                                        По сути куда проще в try() делать flush а на close всегда логировать и игнорировать исключение. Такой код хотя бы читабелен
                                        • 0
                                          Лучше тогда делать close(), а не flush()
                                          • +1
                                            Ну и делать flush/close в try приемлемо, только если у вас один ресурс. Часто бывает два ресурса (и даже три) в одном блоке try
                                            • +1
                                              Нет, так не бывает :)
                                              Один try — один ресурс, иначе никаких гарантий.
                                              То есть нормальный паттерн, в котором нет ни одной описанной Вами проблем, и в то же время нет проблем с checked exception(на примере трех стримов, два инпут и один отпут):

                                              InputStream g1 = new InputStream(...)
                                              try{
                                                //Some actions
                                                OutputStream g2 = new OutputStream(...);
                                                try {
                                                  //Some actions
                                                  InputStream g3 = new InputStream(...);
                                                  try{
                                                    //Some actions
                                                  } finally {
                                                    IOUtils.closeQuietly(g3);
                                                  }
                                                  //Some actions
                                                  g2.flush();
                                                } finally {
                                                   IOUtils.closeQuietly(g2);
                                                }
                                                //Again, some actions
                                              } finally {
                                                IOUtils.closeQuietly(g1);
                                              }
                                              
                                              • 0
                                                В случае с Closer можно обойтись одним try:

                                                Closer closer = Closer.create();
                                                try {
                                                   InputStream g1 = closer.register(new InputStream(...));
                                                   // some actions
                                                   OutputStream g2 = closer.register(new OutputStream(...));
                                                   // some actions
                                                   InputStream g3 = closer.register(new InputStream(...));
                                                   // some actions
                                                } catch (Throwable e) {
                                                   throw closer.rethrow(e);
                                                } finally {
                                                   closer.close();
                                                }
                                                
                                          • –1
                                            Кроме того, пропадает информация об исключении из close(), если основной код выбрасывает исключение.


                                            Разве пропадает? А
                                            e.getTargetException()
                                            не годится?
                                            • 0
                                              getTargetException — это чей метод вообще? В Throwable/Exception нету такого
                                              • 0
                                                Хм, да, не везде оно есть.
                                                Мне оно в рефлекшне помогло реальную причину отлавливать, вот тут InvocationTargetException.
                                                • 0
                                                  Интересно, минус за коммент за то, что я ответил на заданный вопрос, чей метод я имел в виду? ;)
                                            • 0
                                              Если ищем идеал, то openOutputStream() надо вызывать внутри try, так как сам метод openOutputStream, вначале получит outputStream, потом его вернет. Если между «получит» и «вернет» произойдет ошибка, то outputStream не закроется.

                                              Используя в «Правильном решении» «catch (Throwable e)» и «closer.rethrow(e)», идет замена реального исключения на RuntimeException. Иногда так делать нельзя.
                                              • 0
                                                Никогда так делать нельзя…
                                                • 0
                                                  Ну там вообще-то в случае IOException не идёт замены. Оно выбрасывается as-is. Плюс ещё можно добавить одно (или два) «пользовательское» checked исключение, которое тоже не будет оборачиваться в RuntimeException. Это всё костыли конечно же…
                                                • 0
                                                  > Если между «получит» и «вернет» произойдет ошибка, то outputStream не закроется
                                                  Он и с оборачиванием внутри try не закроется, потому что у вас не будет ссылки на него для его закрытия. Вообще не должно быть такого, что между «получит» и «вернет» есть какой-то код, способный выбросить исключение
                                                  • 0
                                                    Ссылка будет:
                                                    OutputStream os = null;
                                                    try {
                                                      os = ...
                                                    } finally {
                                                      ...
                                                    }
                                                    


                                                    Между «получит» и «вернет» обычно всегда есть какой-то код, например, код конструктора: return new BufferedOutputStream(openOutputStream());
                                                    • 0
                                                      Если openOutputStream() кинет исключение, то os = null — вы не сможете закрыть ресурс, на который нету ссылки.
                                                • 0
                                                  А вариант с IOUtils чем плох?

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

                                                  • 0
                                                    Я же объяснил. См. Неправильное решение №3
                                                  • 0
                                                    <del>
                                                    • +2
                                                      } catсh(Throwable t) {

                                                      Как меня утомили такие кэтчи.
                                                      Никогда не ловите Throwable или Error. В случае их возникновения система должна падать в дамп, после чего программист должен узнать причины возникновения этих Error или Throwable и разобраться с ними.
                                                      Конечно можно после падения автоматически поднять новенький процесс. Однако делать это нужно только из другого процесса, или из cron-а.

                                                      А то получается, что поймав Error или Throwable, система не может самостоятельно решить проблему, переходит в нештатное состояние и счастливо тупит мешая работе всех окружающих систем.

                                                      Я даже однажды видел попытку сделать memory dump в случае out of memory вручную путем запуска внешнего процесса из jsp.
                                                      • 0
                                                        В данном случае отлавливание Throwable корректно, потому что оно тут же перебросится наружу. Посмотрите как реализован метод Closer.rethrow()
                                                        • –1
                                                          Нет, не корректно.
                                                          1) В сигнатуре метода появится throws Throwable
                                                          2) В catch ничего нельзя делать, т.к. в случае Error JRE ничего не гарантирует

                                                          Closer.rethrow ничуть не более корректен. Во-первых он пытается создать объект (new RuntimeException(...)), т.е. в случае нехватки памяти код попытается создать новый объект и может получить нехватку памяти, но уже в другом месте. Во-вторых со стеком то же самое.
                                                          Не зря он помечен Beta.
                                                          • 0
                                                            1) Нет, не появится. С чего бы? Сигнатура метода будет throws IOException
                                                            2) Можно, если там не происходит ничего криминального типа выделения памяти в куче. Посмотрите, во что выливается try-with-resources в Java 7. Там есть catch (Throwable t) и даже несколько.

                                                            Closer.rethrow корректен. Он не делает new RuntimeException(), если исключение — это Error. Повторяю еще раз: посмотрите, как реализован метод rethrow. Со стеком проблемы чисто теоретически возможны, но вероятность их близка к нулю настолько, что это можно не учитывать

                                                            • 0
                                                              1) этот код в JDK 1.6 и более старых не пройдет компиляцию пока не добавишь в секцию throws Throwable.
                                                              import java.io.*;
                                                              class tmp {
                                                                  public void myMethod() throws IOException {
                                                                      try {
                                                                          File f = File.createTempFile("",""); // это ради IOException
                                                                          f.delete();
                                                                      } catch (Throwable t) {
                                                                          throw t;
                                                                      }
                                                                  }
                                                              }
                                                              

                                                              2) Что можно? Создавать объекты нельзя, вызывать методы нельзя. И?
                                                              • 0
                                                                1) А вы не смотрите на первое решение. Оно кривое, и так никому не следует писать. Смотрите решение с Closer.rethrow(). Там не будет никакого throws Throwable
                                                                2) Вызывать методы можно, если они дают гарантию, что не будут делать ничего сложного вроде длинной рекурсии или выделения объектов. Closer.rethrow тому пример
                                                        • +1
                                                          Часто catch Error вполен осмысленен.
                                                          Ловить OutOfMemoryError и чистить кэши — достаточно корректное решение. А падать в дамп как правило не вариант.
                                                          Всевозможные LinkageError вылезающие из сторонних библиотек бывают вполне штатными и их надо корректно обработать.
                                                          При работе с reflection — Error-ы это штатные ошибки.
                                                        • +1
                                                          1. Я не понимаю, почему они не объявили rethrow так:

                                                          public <T extends Throwable> T rethrow(T throwable) throws T {/*...*/}
                                                          


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

                                                          2. Почему нужно писать throw closer.rethrow(e);, а не просто closer.rethrow(e);?
                                                          • +1
                                                            1. Потому что так нельзя.
                                                            А можно вот как
                                                            public < T extends Throwable > T rethrow(Class< T > clazz, T throwable) throws T
                                                            
                                                            
                                                            Но собственно такой метод там есть.
                                                            2. Чтобы анализатор кода(в IDE например) понимал что выполнение здесь заканчивается.
                                                            • 0
                                                              1. Почему так нельзя? Скомпилировалось и работает. Или это из-за каких-то идеологических соображений?
                                                              • 0
                                                                Эмм, что именно вы скомпилировали?
                                                                Никаких идеалогических соображений.
                                                                Просто rethrow вызывается в конструкции вида
                                                                } catch(Throwable t) {
                                                                retrhow(t);
                                                                }
                                                                И ваш тип T выводится как Throwable.
                                                                А значит у вызывающего метода должен быть Throwable в списке проверяемых исключений.
                                                                • 0
                                                                  А зачем мне писать Throwable t?

                                                                  Я написал вот так:

                                                                  catch (MyException e) {
                                                                      throw resource.rethrow(e);
                                                                  }
                                                                  
                                                                  И добавил MyException в список. Для ловли всех остальных я бы добавил метод rethrowUnknow c такой сигнатурой, какая у них сейчас.
                                                                  • 0
                                                                    А, ну то есть вы предлагаете городушку вида
                                                                    catch(MyException1 e) {
                                                                    throw rethrowChecked(e);
                                                                    } catch(MyException2 e) {
                                                                    throw rethrowChecked(e);
                                                                    } catch(Throwable t) {
                                                                    throw rethrowOthers(t);
                                                                    }
                                                                    ?
                                                                    Ну так короче вызвать
                                                                    catch(Throwable t) {
                                                                    throw rethrowOthers(t, MyException1.class, MyException2.class);
                                                                    }
                                                                    • 0
                                                                      1. Как я уже написал — нужно будет самому добавлять кидаемые исключения в throws, а не полагаться на компилятор / IDE.
                                                                      2. Исключения разных типов не просто так, их наверно по разному обрабатывать нужно.
                                                                      • 0
                                                                        1. Я не понял.
                                                                        2. Эмм, я не хочу их обрабатывать в этом коде, я хочу пробросить их наверх. И хочу это сделать с минимальной головной болью.
                                                                • 0
                                                                  Единственное, что пришло на ум — из-за multi-exception catches в 1.7+. Но для этого случая методы, которые принимают несколько Class.

                                                                • 0
                                                                  У них все rethrow возвращают RuntimeException, что неудобно, т.к. нужно самому следить, какие исключения кидает метод, чтобы добавить их в throws
                                                                  • 0
                                                                    Прошу прощения IOException, а не RuntimeException в throws стоит.
                                                                • 0
                                                                  3. Почему для 1.6 они просто пишут в лог, а не бросают, к примеру, AggregateIOException extends IOException (самописное), которое бы эмулировало addSuppressed?
                                                                  • 0
                                                                    … А зачем?
                                                                    • –1
                                                                      По вашему исключения только в лог пишутся?
                                                                    • 0
                                                                      А если там не IOException?
                                                                      • 0
                                                                        close кидает IOException, разве нет?
                                                                        • 0
                                                                          Да, но еще ведь есть Error и RuntimeException
                                                                          • –1
                                                                            Вечно они все портят…
                                                                            • 0
                                                                              Они, конечно, есть, но что теперь, каждую строчку в try-catch оборачивать?
                                                                    • 0
                                                                      А так не подойдет? 2 независимых try блока:

                                                                      OutputStream stream;
                                                                      try {
                                                                      stream = openOutputStream();
                                                                      // что-то делаем со stream
                                                                      } catch (Throwable t) {
                                                                      // что-то делаем с исключением
                                                                      }
                                                                      try {
                                                                      if (stream != null) { // Я не уверен, что Java так работает
                                                                      stream.close(); // Или можно просто ловить исключение, если метода нет, вместо if'a
                                                                      }
                                                                      } catch (Throwable unused) {
                                                                      // что-то делаем, если нужно
                                                                      }

                                                                      Простите, теги не работают.
                                                                      • 0
                                                                        (неверно понял, удалил коммент)

                                                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.