Синтаксис пробного использования ресурсов Java 7 (также известный как блок ARM ( Автоматическое управление ресурсами )) хорош, короток и понятен при использовании только одного AutoCloseable
ресурса. Однако я не уверен, какова правильная идиома, когда мне нужно объявить несколько ресурсов, которые зависят друг от друга, например a FileWriter
и a, BufferedWriter
которые обертывают его. Конечно, этот вопрос касается любого случая, когда некоторые AutoCloseable
ресурсы упакованы, а не только эти два конкретных класса.
Я придумал три следующих варианта:
1)
Наивная идиома, которую я видел, состоит в объявлении только оболочки верхнего уровня в переменной, управляемой ARM:
static void printToFile1(String text, File file) {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
bw.write(text);
} catch (IOException ex) {
// handle ex
}
}
Это красиво и коротко, но оно сломано. Поскольку базовый FileWriter
объект не объявлен в переменной, он никогда не будет закрыт непосредственно в сгенерированном finally
блоке. Он будет закрыт только через close
метод упаковки BufferedWriter
. Проблема заключается в том, что если исключение выдается из bw
конструктора, оно close
не будет вызываться и, следовательно, базовое FileWriter
не будет закрыто .
2)
static void printToFile2(String text, File file) {
try (FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw)) {
bw.write(text);
} catch (IOException ex) {
// handle ex
}
}
Здесь и базовый, и ресурс обёртывания объявляются в переменных, управляемых ARM, поэтому они оба обязательно будут закрыты, но базовый fw.close()
будет вызываться дважды : не только напрямую, но и через обтекание bw.close()
.
Это не должно быть проблемой для этих двух конкретных классов, которые оба реализуют Closeable
(что является подтипом AutoCloseable
), чей контракт гласит, что close
разрешены множественные вызовы :
Закрывает этот поток и освобождает любые системные ресурсы, связанные с ним. Если поток уже закрыт, то вызов этого метода не имеет никакого эффекта.
Однако в общем случае у меня могут быть ресурсы, которые реализуют только AutoCloseable
(и не реализуют Closeable
), что не гарантирует, что их close
можно будет вызывать несколько раз:
Обратите внимание, что в отличие от метода закрытия java.io.Closeable, этот метод закрытия не обязательно должен быть идемпотентным. Другими словами, вызов этого метода close более одного раза может иметь некоторый видимый побочный эффект, в отличие от Closeable.close, который не должен иметь эффекта при вызове более одного раза. Однако разработчикам этого интерфейса настоятельно рекомендуется сделать их близкие методы идемпотентными.
3)
static void printToFile3(String text, File file) {
try (FileWriter fw = new FileWriter(file)) {
BufferedWriter bw = new BufferedWriter(fw);
bw.write(text);
} catch (IOException ex) {
// handle ex
}
}
Эта версия должна быть теоретически правильной, потому что только она fw
представляет реальный ресурс, который необходимо очистить. Сам по bw
себе ресурс не содержит никаких ресурсов, он только делегирует его fw
, поэтому его должно быть достаточно только для закрытия базового объекта fw
.
С другой стороны, синтаксис немного нерегулярный, а также, Eclipse выдает предупреждение, которое я считаю ложной тревогой, но это все еще предупреждение, с которым нужно иметь дело:
Утечка ресурсов: «bw» никогда не закрывается
Итак, какой подход пойти? Или я пропустил какую-то другую идиому, которая является правильной ?
источник
public BufferedWriter(Writer out, int sz)
могу броситьIllegalArgumentException
. Кроме того, я могу расширить BufferedWriter классом, который будет генерировать что-то из его конструктора, или создать любую пользовательскую оболочку, которая мне нужна.BufferedWriter
Конструктор может легко бросить исключение.OutOfMemoryError
вероятно, является наиболее распространенным, поскольку он выделяет достаточный объем памяти для буфера (хотя это может означать, что вы хотите перезапустить весь процесс). / Вы должныflush
свой контекстуальный,BufferedWriter
если вы не близко и хотите сохранить содержание ( как правило , только случае , не исключение).FileWriter
Подбирает то, что происходит в кодировке файла по умолчанию - лучше быть явным.Ответы:
Вот мой взгляд на альтернативы:
1)
Для меня лучшее, что пришло на Java из традиционного C ++ 15 лет назад, это то, что вы можете доверять своей программе. Даже если что-то пошло не так, как это часто случается, я хочу, чтобы остальная часть кода работала лучше и пахла розами. В самом деле, здесь
BufferedWriter
может быть исключение. Например, нехватка памяти не будет необычной. Для других декораторов, вы знаете, какой изjava.io
классов-оболочек выдает проверенное исключение из своих конструкторов? Я не. Понимание кода не очень хорошо, если вы полагаетесь на такие неясные знания.Также есть «разрушение». Если возникает ошибка, то вы, вероятно, не хотите сбрасывать мусор в файл, который необходимо удалить (код для этого не показан). Хотя, конечно, удаление файла - еще одна интересная операция, связанная с обработкой ошибок.
Обычно вы хотите, чтобы
finally
блоки были максимально короткими и надежными. Добавление флешей не помогает этой цели. Во многих выпусках некоторые из классов буферизации в JDK имели ошибку, из-flush
за которой неclose
вызывалось исключение изнутри, вызванноеclose
для декорированного объекта. Хотя это было исправлено в течение некоторого времени, ожидайте его от других реализаций.2)
Мы все еще очищаем неявный блок finally (теперь с повторением
close
- это ухудшается, когда вы добавляете больше декораторов), но конструкция безопасна, и мы должны неявно блокировать блоки finally, чтобы даже сбойflush
не препятствовал освобождению ресурса.3)
Здесь есть ошибка. Должно быть:
Некоторые плохо реализованные декораторы фактически являются ресурсом и должны быть надежно закрыты. Кроме того, некоторые потоки, возможно, должны быть закрыты определенным образом (возможно, они выполняют сжатие и должны записывать биты для завершения, и не могут просто очистить все.
решение суда
Хотя 3 - технически превосходное решение, причины разработки программного обеспечения делают 2 лучшим выбором. Однако try-with-resource все еще неадекватное исправление, и вам следует придерживаться идиомы Execute Around , которая должна иметь более четкий синтаксис с замыканиями в Java SE 8.
источник
Первый стиль - тот, который предложен Oracle .
BufferedWriter
не генерирует проверенные исключения, поэтому, если выдается какое-либо исключение, программа не должна восстанавливаться после него, что делает восстановление ресурса в основном спорным.Главным образом из-за того, что это могло произойти в потоке, когда поток умирает, но программа все еще продолжается - скажем, произошел временный сбой памяти, которого не хватило для того, чтобы серьезно повредить остальную часть программы. Однако это довольно сложный случай, и если это случается достаточно часто, чтобы сделать утечку ресурсов проблемой, попытка использования ресурсов - это наименьшая из ваших проблем.
источник
Вариант 4
Измените ваши ресурсы, чтобы они были закрываемыми, а не автоматически закрываемыми, если вы можете. Тот факт, что конструкторы могут быть объединены в цепочку, подразумевает, что нет ничего удивительного в том, чтобы дважды закрыть ресурс. (Это было верно и до ARM.) Подробнее об этом ниже.
Вариант 5
Не используйте ARM и код очень осторожно, чтобы close () не вызывался дважды!
Вариант 6
Не используйте ARM и сделайте ваши вызовы close () в попытке / поймать себя.
Почему я не думаю, что эта проблема уникальна для ARM
Во всех этих примерах вызовы finally () должны находиться в блоке catch. Оставлено для удобства чтения.
Ничего хорошего, потому что fw можно закрыть дважды. (что хорошо для FileWriter, но не в вашем гипотетическом примере):
Не хорошо, потому что fw не закрывается, если исключение при создании BufferedWriter. (опять не может произойти, но в вашем гипотетическом примере):
источник
Я просто хотел основываться на предложении Жанны Боярской не использовать ARM, но убедиться, что FileWriter всегда закрывается ровно один раз. Не думайте, что здесь есть какие-то проблемы ...
Я думаю, поскольку ARM - это просто синтаксический сахар, мы не всегда можем использовать его для замены блоков finally. Так же, как мы не всегда можем использовать цикл for-each, чтобы сделать что-то, что возможно с итераторами.
источник
try
иfinally
блоки генерируют исключения, эта конструкция потеряет первую (и потенциально более полезную).Чтобы согласиться с более ранними комментариями: простейшим является (2) использовать
Closeable
ресурсы и объявлять их по порядку в предложении try-with-resources. Если у вас есть толькоAutoCloseable
, вы можете обернуть их в другой (вложенный) класс, который просто проверяет, чтоclose
вызывается только один раз (Facade Pattern), например, имеяprivate bool isClosed;
. На практике даже Oracle просто (1) объединяет в цепочку конструкторы и неправильно обрабатывает исключения во время цепочки.Кроме того, вы можете вручную создать связанный ресурс, используя статический метод фабрики; это инкапсулирует цепочку и выполняет очистку, если она не проходит частично:
Затем вы можете использовать его как отдельный ресурс в предложении try-with-resources:
Сложность возникает из-за обработки нескольких исключений; в противном случае это просто «закрыть ресурсы, которые вы приобрели до сих пор». Обычная практика заключается в том, чтобы сначала инициализировать переменную, которая содержит объект, который содержит ресурс для
null
(здесьfileWriter
), а затем включить нулевую проверку в очистке, но это кажется ненужным: если конструктор завершается ошибкой, нечего очищать, поэтому мы можем просто позволить этому исключению распространяться, что немного упрощает код.Вы могли бы сделать это в общем:
Точно так же вы можете связать три ресурса и т. Д.
Если не считать математики, вы могли бы даже трижды объединить в цепочку, объединив два ресурса за раз, и это было бы ассоциативно, то есть вы получите один и тот же объект при успехе (потому что конструкторы ассоциативны), и те же исключения, если произошел сбой в любом из конструкторов. Предполагая, что вы добавили S в вышеуказанную цепочку (поэтому вы начинаете с V и заканчиваете S , поочередно применяя U , T и S ), вы получите то же самое, если сначала соедините S и T , а затем U , соответствует (ST) U , или если вы сначала приковали цепью T и U , тоS, соответствующий S (ТУ) . Однако было бы яснее просто записать явную тройную цепочку в одной фабричной функции.
источник
try (BufferedWriter writer = <BufferedWriter, FileWriter>createChainedResource(file)) { /* work with writer */ }
?Так как ваши ресурсы вложены, ваши предложения try-with также должны быть:
источник
close
будет вызываться дважды.Я бы сказал, не используйте ARM и продолжайте с Closeable. Используйте метод, как,
Также вы должны рассмотреть возможность вызова close, так
BufferedWriter
как это не просто делегирование closeFileWriter
, но и некоторая очистка, напримерflushBuffer
.источник
Мое решение состоит в том, чтобы сделать рефакторинг «метод извлечения», как показано ниже:
printToFile
может быть написано либоили
Для дизайнеров классов lib я предложу им расширить
AutoClosable
интерфейс дополнительным методом для подавления закрытия. В этом случае мы можем вручную контролировать поведение закрытия.Для языковых дизайнеров урок заключается в том, что добавление новой функции может означать добавление множества других. В этом случае Java, очевидно, функция ARM будет работать лучше с механизмом передачи владения ресурсами.
ОБНОВИТЬ
Первоначально код выше требует,
@SuppressWarning
так какBufferedWriter
внутри функции требуетсяclose()
.Как следует из комментария, если
flush()
он вызывается до закрытия программы записи, мы должны делать это перед любымиreturn
(неявными или явными) инструкциями внутри блока try. Я думаю, что в настоящее время нет способа гарантировать, что вызывающий абонент делает это, так что это должно быть задокументированоwriteFileWriter
.ОБНОВЛЕНИЕ СНОВА
Приведенное выше обновление делает
@SuppressWarning
ненужным, поскольку требует, чтобы функция возвращала ресурс вызывающей стороне, поэтому само закрытие не обязательно. К сожалению, это возвращает нас к началу ситуации: предупреждение теперь перенесено обратно на сторону звонящего.Таким образом , чтобы правильно решить эту проблему, нам нужно настроить ,
AutoClosable
что всякий раз , когда он закрывается, подчеркиваниеBufferedWriter
должно быть подflush()
ред. На самом деле, это показывает нам еще один способ обойти предупреждение, посколькуBufferWriter
ни в коем случае не закрывается.источник
bw
самом деле эти данные будут записаны? После этого он буферизуется, поэтому ему иногда нужно только записать на диск (когда буфер заполнен и / или включен,flush()
иclose()
методы). Я думаю, чтоflush()
метод должен быть вызван. Но тогда нет необходимости использовать буферизованный писатель, так как мы записываем его сразу в одном пакете. И если вы не исправите код, вы можете получить данные, записанные в файл в неправильном порядке, или вообще не записанные в файл.flush()
требуется вызвать, это должно произойти всякий раз, когда вызывающий абонент решил закрытьFileWriter
. Так что это должно произойтиprintToFile
прямо перед любымreturn
s в блоке try. Это не должно быть частью,writeFileWriter
и, следовательно, предупреждение касается не чего-то внутри этой функции, а вызывающей стороны этой функции. Если у нас есть аннотация,@LiftWarningToCaller("wanrningXXX")
то поможет этот случай и тому подобное.