Во многих Java программ, для закрытия файлов, которые больше не нужны, метод close() вызывается явным образом.
Такой способ закрытия файлов используется еще с тех пор, как вышла первая версия Java. Именно поэтому он часто встречается в существующих программах. Более того, он до сих пор остается вполне оправданным и полезным.
Однако в версию JDK 7 включено новое средство, предоставляющее другой, более рациональный способ управления ресурсами,в том числе и потоками файлового ввода-вывода, автоматизирующий процесс закрытия файлов.
Этот способ основывается на новой разновидности оператора try, называемой оператором try с ресурсами, а иногда еще — автоматическим управлением ресурсами.
Главное преимущество оператора try с ресурсами заключается в том, что он предотвращает ситуации, в которых файл (или другой ресурс) непреднамеренно остается неосвобожденным и после того, как необходимость в его использовании отпала.
Внимание! Если не позаботиться о своевременном закрытии файлов, то это может привести к утечке памяти и прочим осложнениям в работе программы.
Так выглядит общая форма оператора try с ресурсами:
1 2 3 |
try(описание_ресурса) { // использовать ресурс } |
Здесь описание_ресурса включает в себя объявление и инициализацию ресурса, такого как файл. По сути, в это описание входит объявление переменной, которая инициализируется ссылкой на объект управляемого ресурса.
По завершении блока try объявленный ресурс автоматически освобождается. Если этим ресурсом является файл, то он автоматически закрывается, что избавляет от необходимости вызывать метод close() явным образом. Оператор try с ресурсами также может включать блоки catch и finally.
Область применимости таких операторов try ограничена ресурсами, которые реализуют интерфейс AutoCloseable, определенный в пакете java.lang.
В этом интерфейсе определен метод close(). Интерфейс AutoCloseable наследуется интерфейсом Closeable, определенным в пакете java.io. Оба интерфейса реализуются классами потоков, в том числе FileinputStream и FileOutputStream. Следовательно, оператор try с ресурсами может применяться вместе с потоками, включая потоки файлового ввода-вывода.
В качестве примера ниже приведена версия программы, в которой оператор try с ресурсами используется для автоматического закрытия файла.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
import java.io.*; class ShowFile { public static void main(String args[]) { int i; // Прежде всего необходимо убедиться в том, что программе // передаются имена обоих файлов if(args.length !=1) { System.out.println("Использование: ShowFile имя_файла"); return; } // Использование try с ресурсами для открытия // файла с последующим его закрытием после того, // как будет покинут блок try try(FileInputStream fin = new FileInputStream(args[0])) { do { i = fin.read(); if(i != -1) System.out.print((char) i); } while(i != -1); } catch(IOException exc) { System.out.println("Ошибка ввода-вывода: " + exc); } } } |
Обратите внимание на то, как открывается файл в операторе try с ресурсами:
1 |
try(FileInputStream fin = new FileInputStream(args[0])) { |
Здесь сначала объявляется переменная fin типа FileinputStream, а затем этой переменной присваивается ссылка на файл, который выступает в роли объекта, открываемого с помощью конструктора класса FileinputStream.
Таким образом, в данной версии программы переменная fin является локальной по отношению к блоку try и создается при входе в этот блок. При выходе из блока try файл, связанный с переменной fin, автоматически закрывается с помощью неявно вызываемого метода close().
Это означает, что теперь отсутствует риск того, что вы забудете закрыть файл путем явного вызова метода close() . В этом и состоит главное преимущество автоматического управления ресурсами.
Важно понимать, что ресурс, объявленный в операторе try, неявно принимает модификатор final. Это означает, что после создания ресурсной переменной ее значение не может быть изменено. Кроме того, ее область действия ограничивается блоком оператора try.
С помощью одного подобного оператора try можно управлять несколькими ресурсами. Для этого достаточно указать список объявлений ресурсов, разделенных точкой с запятой.
В качестве примера ниже приведена версия программы CopyFile. В этой версии оба ресурса, fin и fout, управляются одним оператором try.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import java.io.*; class CopyFile { public static void main(String args[]) throws IOException{ int i; // Прежде всего необходимо убедиться в том, что программе // передаются имена обоих файлов if(args.length !=2) { System.out.println("Использование: CopyFile откуда куда"); return; } // открытие двух файлов и управление ими с помощью оператора try try(FileInputStream fin = new FileInputStream(args[0]); FileOutputStream fout = new FileOutputStream(args[1])) { do { i = fin.read(); if(i != -1) fout.write(i); } while(i != -1); } catch(IOException exc) { System.out.println("Ошибка ввода-вывода: " + exc); } } } |
Обратите внимание на то, как входной и выходной файлы открываются в операторе:
1 2 |
try(FileInputStream fin = new FileInputStream(args[0]); FileOutputStream fout = new FileOutputStream(args[1])) { |
По завершении этого блока try оба файла, на которые ссылаются переменные fin и fout, будут автоматически закрыты.
Если сравнить эту версию программы с предыдущей, то можно заметить, что ее исходный код намного компактнее. Возможность создания более компактного кода является еще одним, дополнительным преимуществом оператора try с ресурсами.
Стоит упомянуть еще об одной особенности оператора try с ресурсами. Вообще говоря, возникшее при выполнении блока try исключение может породить другое исключение при закрытии ресурса в блоке finally.
В случае «обычного» оператора try первоначальное исключение теряется, будучи прерванным вторым исключением. Но в случае оператора try с ресурсами второе исключение подавляется.
При этом оно не теряется, а просто добавляется в список подавленных исключений, связанных с первым исключением. Этот список можно получить, вызвав метод getSuppressed(), определенный в классе Throwable.
Не менее важным остается и умение использовать традиционный способ освобождения ресурсов с помощью явного вызова метода close(). И на то имеется ряд веских причин.
Во-первых, среди уже существующих и повсеместно эксплуатируемых программ на Java немало таких, в которых применяется традиционный способ управления ресурсами. Поэтому вы должны как следует усвоить традиционный подход и уметь использовать его для сопровождения устаревшего кода.
Во-вторых, переход к использованию версии JDK 7 может произойти не сразу, а следовательно, вы будете вынуждены работать с предыдущей версией данного комплекта. В этом случае воспользоваться преимуществами оператора try с ресурсами не удастся, и придется применять традиционный способ управления ресурсами.
И наконец, в некоторых случаях закрытие ресурса явным образом оказывается более эффективным, чем его автоматическое освобождение.
И все же, если вы работаете с версией JDK 7, JDK 8 или более поздней, вариант автоматического управления ресурсами, как более рациональный и надежный, следует считать предпочтительным.