Reader.close: примеры (JAVA)

Метод close для Reader: разбор
Раздел: Потоки ввода-вывода (Streams) символьные
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()
  • Более общий интерфейс, возвращающий throws Exception. Используется для объектов, которые должны закрываться в try-with-resources и допускают выброс общих исключений.

  • Channel.close() (NIO)
  • Используется для закрытия каналов в java.nio.channels. Поддерживает неблокирующие операции и отличные семантики закрытия.

  • Files.newBufferedReader / Files.lines
  • Утилиты 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: ...
  • JavaScript (Node.js)
  • 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'.
  • PHP
  • $f = fopen('example.txt', 'r');
    echo fgetc($f);
    fclose($f);
    Печать первого символа и закрытие дескриптора.
  • C#
  • IDisposable.Dispose используется вместо явного close; using-блок реализует автоматическое освобождение.

    using (var r = new System.IO.StreamReader("example.txt"))
    {
        Console.WriteLine((char)r.Read());
    }
    Печатает символ и выполняет Dispose (аналог close).
  • Go
  • Интерфейс 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 гарантирует закрытие.
  • Kotlin
  • Используется Closeable и extension-функция use { } для автоматического закрытия.

    val reader = File("example.txt").bufferedReader()
    reader.use {
        println(it.read().toChar())
    }
    Печать первого символа, use закрывает reader автоматически.
  • Lua
  • 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) {}
    }
    Исключение при закрытии теряется, дебаг усложняется.
  • Неправильная обработка suppressed исключений
  • В 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
  • 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
  • Введена конструкция, упрощающая корректное закрытие ресурсов. Closeable реализует AutoCloseable, что позволяет автоматическое освобождение.

  • NIO.2 (Java 7) - Files и Paths
  • Добавлены удобные методы создания Reader через Files.newBufferedReader, а также Streams API для построчной обработки файлов.

  • Отсутствие изменений сигнатуры
  • Сигнатура close() по-прежнему возвращает void и бросает IOException; несовместимых изменений не было.

Расширенные и нестандартные примеры

Несколько продвинутых сценариев, демонстрирующих нюансы работы close.

1) Создание обёртки, предотвращающей закрытие внутреннего Reader (CloseShield)

Пример java
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) Обработка нескольких ресурсов и подавленные исключения

Пример java
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 для освобождения дополнительных ресурсов

Пример java
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) Закрытие из другого потока - координация

Пример java
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()

Пример java
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 поверх канала

Пример java
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, если реализация это поддерживает.

джава Reader.close function comments

En
Reader.close Closes the stream