Классы ресурсов. I/O Streams
Дорогу осилит идущийСегодня мы приступаем к достаточно обширной теме, связанной с ресурсами в Java. Ресурсами (внешними ресурсами) называют некие хранилища информации вне JVM. Это могут быть файлы, базы данных и пр.
Также вы можете встретить употребление "ресурс" по отношению к классу (или его объектам), предназначенному для работы с внешними ресурсами.
Внутренние ресурсы - это объекты, хранящиеся в памяти JVM (отсюда и разделение на внутренние и внешние ресурсы), но вряд ли вам когда-нибудь понадобится эта информация.
Ресурсы. Классы для работы с ресурсами
Для работы с ресурсами в Java (а также сторонних библиотеках и фреймворках) существует огромное количество различных классов. Их объединяет две вещи:
- Все они предназначены для взаимодействия с ресурсами (неожиданно);
- Все они являются наследниками интерфейса AutoCloseable (или его наследника Closeable).
AutoCloseable имеет лишь один метод: close(). Он необходим для закрытия ресурса.
На самом деле, мы опосредовано знакомы с некоторыми классами ресурсов:
- InputStream. Статическое поле in у класса System имеет именно такой тип;
- PrintStream. Одна из реализаций класса OutputStream. PrintStream – тип статического поля out в классе System;
- Scanner. Строго говоря, он является не ресурсом, а оберткой над ресурсом, но как класс, расширяющий Closeable, он относится к классам для работы с ресурсами. Метод close() будет вызывать close() у ресурса, который был указан в конструкторе Scanner'а.
Try-with-resource
Знать, что используемый вами класс, является ресурсом, важно по одной простой причине: ресурсы необходимо закрывать, чтобы избежать утечек памяти.
Скажем, если вы создали InputStream, читающий данные из файла – в памяти Java будет храниться содержимое файла, с которым работает ваш InputStream. Это может быть не критично в маленьких программах, имеющих избыток оперативной памяти. Но в больших приложениях это приведет как минимум к замедлению работы.
Но если вы создали OutputStream, пишущий в файл - никто не сможет писать в этот же файл, пока ваш OutputStream не будет закрыт. Опять же, в небольших однопоточных (в контексте многопоточности, а не потоков ввода-вывода) приложениях это может быть не ощутимо, но в больших приложениях приведет к проблемам доступа, замедлению работы или более серьезным проблемам.
С завершением программы, ресурс будет закрыт силами операционной системы. Но если используемый вами ресурс имеет специфическую логику при закрытии (а это вполне возможно), она не отработает, что может привести к дальнейшим ошибкам.
Таким образом, для любых объектов классов, расширяющих AutoCloseable или Closeable требуется вызывать метод close() сразу после того, как работа с ресурсом завершена.
До Java 7 такую логику обычно оборачивали в try-catch:
InputStream in = null;//создание объекта может выбрасывать исключение, первично присваиваем переменной значение null
try{
in = …//инициализация переменной in
//Работа с переменной in
} catch(IOException e){
//IOException – ошибка ввода/вывода. В большинстве случаев методы ресурсов будут throws IOException (или его наследников)
//Логика обработки ошибки
} finally {
in.close(); //при условии, что метод, где это происходит, является throws Exception (или IOException)
}
Закрытие производится в finally, т.к. при выпадении ошибки код в try может быть выполнен не до конца и нет гарантии, что метод close() будет вызван, если расположить его там. Но сам close() тоже может выбросить исключение (В AutoCloseable он помечен как throws Exception, в Cloaseable – throws IOException).
Поэтому метод, работающий ресурсами должен был либо иметь блок throws, либо finally из примера выше становился более раздутым:
…
finally {
try {
if(in != null) {//первично переменная была инициализирована null, мы не имеем гарантии, что дальнейшая инициализация была успешна
in.close();
}
} catch(IOException ex){
//Логика обработки ошибки
}
}
Однако такой код был настолько объемным и настолько однотипным, что в Java 7 появилась отдельная синтаксическая конструкция, инкапсулирующая в себе работу с классами ресурсов – try-with-resource. Теперь код выше можно переписать так:
try(InputStream in = ...//инициализация переменной in) {
//Работа с переменной in
} catch(IOException e){
//Логика обработки ошибки
}
Таким образом, Java берет на себя закрытие ресурса, который создается в скобках после try. Также в () может быть помещено несколько ресурсов, в таком случае необходимо их указать через точку с запятой:
try(InputStream in1 = …; InputStream in2 = …)
Если в рамках этого подраздела что-то осталось непонятным – могу предложить статью на метанит: https://metanit.com/java/tutorial/6.2.php
В целом, там примерно то же самое, но, возможно, кому-то будет легче осознать на более живых примерах.
I/O Streams. Reader и Writer
В рамках этого подраздела мы также обратимся к метаниту.
Статья, посвященная знакомству с потоками ввода и вывода не слишком сложная. Но сразу замечу, что заучивать все иерархию наследования Input- и Output- Stream нет необходимости. То же актуально и для Reader и Writer.
Кроме того, советую сформировать в голове ассоциацию:
InputStream и Reader – классы, которые читают ресурс. Т.е. передают информацию из ресурса в Java, ее можно будет записать в переменные.
OutputStream и Writer – классы, пишущие в ресурс. Они позволяют значение Java-переменных записать в ресурс.
На этом переходим к статье: https://metanit.com/java/tutorial/6.1.php
В ближайших уроках мы познакомимся с обработкой файлов, на данном этапе можете взглянуть на синтаксис, позволяющий работать с ними через InputStream (есть в примерах статьи выше).
С теорией на сегодня все!

Я не вижу смысла давать полноценные задания по этой теме, I/O Streams и Reader /Writer достаточно низкоуровневая логика, работа с ней нужна не часто, в большинстве случаев, она сводится к вызову readAllBytes() у InputStream.
Однако для закрепления материала предлагаю попытаться считать какие-нибудь значения из консоли через System.in и поприводить их к переменным разных примитивных типов или строкам. Это может быть достаточно увлекательно, хоть и не сложно.
Подсказка: у String есть конструктор, принимающий byte[]. Как удалить из строки лишние символы вы тоже уже знаете:)
Если что-то непонятно или не получается – welcome в комменты к посту или в лс:)
Канал: https://t.me/+relA0-qlUYAxZjI6
Мой тг: https://t.me/ironicMotherfucker
Дорогу осилит идущий!