Reader.close: примеры (JAVA)
Reader.close: voidОбщее описание метода
Метод close() у java.io.Reader предназначен для освобождения ресурсов, связанных с объектом чтения (например, дескриптора файла или сетевого соединения). Он определяется в интерфейсе java.io.Closeable и возвращает void. В большинстве реализаций закрытие Reader приводит к закрытию связанных потоков или каналов и делает объект непригодным для дальнейшего чтения: последующие вызовы методов чтения обычно бросают IOException.
Сигнатура:
public void close() throws java.io.IOException
Аргументы: метод не принимает аргументов.
Возвращаемое значение: ничего не возвращает (void).
Поведение и особенности:
- При вызове может быть выброшено java.io.IOException - следует обрабатывать или пробрасывать исключение.
- Reader реализует Closeable, а Closeable наследует AutoCloseable - это позволяет применять try-with-resources (начиная с Java 7).
- Некоторые реализации (например, BufferedReader, InputStreamReader, FileReader) при закрытии также закрывают связанный InputStream/Reader, другие пользователи могут переопределять поведение.
- Повторный вызов close обычно безопасен - большинство реализаций игнорируют второй вызов, но это не гарантируется спецификацией и зависит от реализации.
- Закрытие может генерировать исключения, которые могут стать подавленными (suppressed) при использовании try-with-resources, если в блоке try также выброшено исключение.
Когда используется
close применяется всякий раз, когда нужно завершить работу с источником символов и освободить системные ресурсы: при работе с файлами, сетевыми потоками, внешними библиотеками, при длительных операциях ввода-вывода.
Короткие примеры использования
Примеры показывают разные варианты закрытия Reader и поведение при исключениях.
1) try-with-resources с FileReader
import java.io.*;
try (Reader r = new FileReader("example.txt")) {
int ch = r.read();
System.out.println(ch);
} catch (IOException e) {
e.printStackTrace();
}
Если файл существует - печатает код первого символа, затем ресурс закрывается автоматически.
2) try-finally и явный close
Reader r = null;
try {
r = new BufferedReader(new FileReader("example.txt"));
// чтение
} catch (IOException e) {
e.printStackTrace();
} finally {
if (r != null) {
try {
r.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
Ресурс закрывается в finally, возможные ошибки при закрытии обрабатываются отдельно.
3) StringReader (закрытие не обязательно, но допустимо)
Reader r = new java.io.StringReader("abc");
try {
System.out.println((char) r.read());
} finally {
r.close();
}
Вывод 'a'. StringReader.close освобождает ресурсы, но фактически не имеет внешних дескрипторов.
Похожие механизмы в Java
Внутри Java существуют близкие по назначению методы и интерфейсы:
- InputStream.close()
- AutoCloseable.close()
- Channel.close() (NIO)
- Files.newBufferedReader / Files.lines
Аналогичный метод для байтовых потоков. Предпочтителен при работе с бинарными данными.
Более общий интерфейс, возвращающий throws Exception. Используется для объектов, которые должны закрываться в try-with-resources и допускают выброс общих исключений.
Используется для закрытия каналов в java.nio.channels. Поддерживает неблокирующие операции и отличные семантики закрытия.
Утилиты NIO.2, возвращающие Reader или Stream<String>, интегрированные с файловой системой и поддерживающие charset. Удобнее при работе с современным API.
Выбор зависит от конкретной задачи: для текстового ввода - Reader и его подклассы; для байтов - InputStream; для современных операций с файлами и потоковыми линиями - NIO API.
Альтернативы в других языках и отличия
Краткие соответствия и примеры на разных языках.
- Python
Контекстный менеджер с методом close() у файлов. Рекомендуется use with statement.
f = open('example.txt', 'r')
try:
print(f.read(1))
finally:
f.close()
Печатает первый символ и закрывает файл. Чаще пишут: with open('example.txt') as f: ...
fs.close для дескриптора; при использовании createReadStream рекомендуется вызывать destroy или слушать событие close.
const fs = require('fs');
const rs = fs.createReadStream('example.txt');
rs.on('data', chunk => console.log(chunk[0]));
rs.on('close', () => console.log('closed'));
rs.destroy();
Выводит байт и сообщение 'closed'.
$f = fopen('example.txt', 'r');
echo fgetc($f);
fclose($f);
Печать первого символа и закрытие дескриптора.
IDisposable.Dispose используется вместо явного close; using-блок реализует автоматическое освобождение.
using (var r = new System.IO.StreamReader("example.txt"))
{
Console.WriteLine((char)r.Read());
}
Печатает символ и выполняет Dispose (аналог close).
Интерфейс io.Closer с методом Close() error. Рекомендуется вызывать defer f.Close().
f, _ := os.Open("example.txt")
defer f.Close()
buf := make([]byte, 1)
f.Read(buf)
fmt.Println(string(buf))
Печатает первый символ, defer гарантирует закрытие.
Используется Closeable и extension-функция use { } для автоматического закрытия.
val reader = File("example.txt").bufferedReader()
reader.use {
println(it.read().toChar())
}
Печать первого символа, use закрывает reader автоматически.
local f = io.open('example.txt', 'r')
print(f:read(1))
f:close()
Читает и закрывает файл.
Отличия в семантике: в Java close() объявлен с throws IOException и интегрирован с try-with-resources; в Go Close возвращает error; в C# метод называется Dispose и работает через using.
Типичные ошибки при использовании
- Забыть закрыть ресурс
Приводит к утечкам дескрипторов и исчерпанию системных ресурсов. Пример ошибки:
public void bad() throws IOException {
Reader r = new FileReader("example.txt");
r.read();
// отсутствует close
}
После вызова остаются открытые дескрипторы - риск утечки.
Подавление исключения может скрыть реальные проблемы. Пример:
Reader r = null;
try {
r = new FileReader("example.txt");
// чтение
} finally {
try { r.close(); } catch (IOException ignored) {}
}
Исключение при закрытии теряется, дебаг усложняется.
В try-with-resources исключение в close может стать suppressed, если раньше произошло исключение. Пример:
class BadReader extends Reader {
public int read(char[] cbuf, int off, int len) throws IOException { throw new IOException("read error"); }
public void close() throws IOException { throw new IOException("close error"); }
public boolean ready() { return false; }
}
try (Reader r = new BadReader()) {
r.read(new char[10]);
} catch (IOException e) {
System.out.println(e.getMessage());
for (Throwable t : e.getSuppressed()) System.out.println("supp: " + t.getMessage());
}
Вывод: read error supp: close error
Reader r = new StringReader("a");
r.close();
try { r.read(); } catch (IOException e) { System.out.println("err: " + e.getMessage()); }
В большинстве реализаций будет выброшено IOException или возвращено -1 в зависимости от реализации.
Изменения и эволюция в Java
Сам метод close у Reader API кардинально не изменялся в поздних версиях Java. Основные связанные изменения и дополнения:
- Java 7 - try-with-resources и AutoCloseable
- NIO.2 (Java 7) - Files и Paths
- Отсутствие изменений сигнатуры
Введена конструкция, упрощающая корректное закрытие ресурсов. Closeable реализует AutoCloseable, что позволяет автоматическое освобождение.
Добавлены удобные методы создания Reader через Files.newBufferedReader, а также Streams API для построчной обработки файлов.
Сигнатура close() по-прежнему возвращает void и бросает IOException; несовместимых изменений не было.
Расширенные и нестандартные примеры
Несколько продвинутых сценариев, демонстрирующих нюансы работы close.
1) Создание обёртки, предотвращающей закрытие внутреннего Reader (CloseShield)
import java.io.*;
class CloseShieldReader extends Reader {
private final Reader delegate;
CloseShieldReader(Reader r) { this.delegate = r; }
public int read(char[] cbuf, int off, int len) throws IOException { return delegate.read(cbuf, off, len); }
public void close() throws IOException { /* намеренно не закрывать delegate */ }
}
Reader base = new FileReader("example.txt");
Reader shield = new CloseShieldReader(base);
shield.close();
System.out.println(base.read());
base.close();
Позволяет временно предотвратить закрытие базового Reader - полезно при передаче ресурса другому коду.
2) Обработка нескольких ресурсов и подавленные исключения
class R1 extends Reader {
public int read(char[] cbuf, int off, int len) throws IOException { throw new IOException("read1"); }
public void close() throws IOException { throw new IOException("close1"); }
}
class R2 extends Reader {
public int read(char[] cbuf, int off, int len) { return -1; }
public void close() throws IOException { throw new IOException("close2"); }
}
try (Reader r1 = new R1(); Reader r2 = new R2()) {
r1.read(new char[1]);
} catch (IOException e) {
System.out.println(e.getMessage());
for (Throwable t : e.getSuppressed()) System.out.println("supp: " + t.getMessage());
}
Вывод: read1 supp: close1 supp: close2 (точный порядок suppressed может зависеть от реализации JVM)
3) Переопределение close для освобождения дополнительных ресурсов
class MyReader extends BufferedReader {
private final SomeNativeHandle handle;
MyReader(Reader in, SomeNativeHandle h) { super(in); this.handle = h; }
@Override
public void close() throws IOException {
try {
super.close();
} finally {
handle.release();
}
}
}
// Пример использования покажет, что native-ресурс освобожден в любом случае.
При возникновении исключения в super.close() native-ресурс всё равно освобождается в finally.
4) Закрытие из другого потока - координация
PipedReader pr = new PipedReader();
PipedWriter pw = new PipedWriter(pr);
Thread readerThread = new Thread(() -> {
try (Reader r = pr) {
int x;
while ((x = r.read()) != -1) System.out.println(x);
} catch (IOException e) { e.printStackTrace(); }
});
readerThread.start();
// закрытие из основного потока
pw.close(); // приведёт к завершению чтения и закрытию связанного reader
При закрытии PipedWriter чтение в другом потоке завершается - взаимозависимость потоков учитывается.
5) Отладка: логирование при close()
class VerboseReader extends FileReader {
public VerboseReader(String file) throws FileNotFoundException { super(file); }
@Override
public void close() throws IOException {
System.out.println("closing");
super.close();
}
}
try (Reader r = new VerboseReader("example.txt")) { r.read(); }
Выводит 'closing' перед фактическим закрытием, помогает в диагностике порядка вызовов.
6) Интеграция с NIO: Reader поверх канала
ReadableByteChannel ch = FileChannel.open(Paths.get("example.txt"));
Reader r = Channels.newReader(ch, StandardCharsets.UTF_8.newDecoder(), -1);
try (Reader rr = r) {
System.out.println(rr.read());
}
Reader закрывает канал при вызове close, если реализация это поддерживает.