Рубрики
Без рубрики

Должны ли мы закрыть поток Java?

Вы когда-нибудь задумывались, важно ли закрыть поток Java 8?

Автор оригинала: Mona Mohamadinia.

1. Обзор

С введением лямбда-выражений в Java 8 стало возможным писать код более лаконичным и функциональным способом. Потоки и функциональные интерфейсы являются сердцем этого революционного изменения в платформе Java.

В этом кратком руководстве мы узнаем, следует ли явно закрывать потоки Java 8, рассматривая их с точки зрения ресурсов.

2. Закрытие потоков

Java 8 потоки реализуют автоклавируемый интерфейс:

public interface Stream extends BaseStream<...> {
    // omitted
}
public interface BaseStream<...> extends AutoCloseable {
    // omitted
}

Проще говоря, мы должны думать о потоках как о ресурсах, которые мы можем заимствовать и возвращать, когда закончим с ними. В отличие от большинства ресурсов, нам не нужно всегда закрывать потоки.

Поначалу это может показаться нелогичным, поэтому давайте посмотрим, когда мы должны и когда не должны закрывать потоки Java 8.

2.1. Коллекции, массивы и генераторы

Большую часть времени мы создаем экземпляры Stream из коллекций Java, массивов или функций генератора. Например, здесь мы работаем с коллекцией String через Stream API:

List colors = List.of("Red", "Blue", "Green")
  .stream()
  .filter(c -> c.length() > 4)
  .map(String::toUpperCase)
  .collect(Collectors.toList());

Иногда мы генерируем конечный или бесконечный последовательный поток:

Random random = new Random();
random.ints().takeWhile(i -> i < 1000).forEach(System.out::println);

Кроме того, мы также можем использовать потоки на основе массивов:

String[] colors = {"Red", "Blue", "Green"};
Arrays.stream(colors).map(String::toUpperCase).toArray()

Когда мы имеем дело с такого рода потоками, мы не должны закрывать их явно. Единственным ценным ресурсом, связанным с этими потоками, является память, и Сборка мусора (GC) заботится об этом автоматически.

2.2. Ресурсы ввода-вывода

Однако некоторые потоки поддерживаются ресурсами ввода-вывода, такими как файлы или сокеты. Например, метод Files.lines() передает потоки всех строк для данного файла:

Files.lines(Paths.get("/path/to/file"))
  .flatMap(line -> Arrays.stream(line.split(",")))
  // omitted

Под капотом этот метод открывает экземпляр FileChannel , а затем закрывает его при закрытии потока. Таким образом, если мы забудем закрыть поток, базовый канал останется открытым, и тогда мы получим утечку ресурсов .

Чтобы предотвратить такие утечки ресурсов, настоятельно рекомендуется использовать идиому try-with-resources для закрытия потоков на основе ввода-вывода:

try (Stream lines = Files.lines(Paths.get("/path/to/file"))) {
    lines.flatMap(line -> Arrays.stream(line.split(","))) // omitted
}

Таким образом, компилятор автоматически закроет канал. Ключевым моментом здесь является закрытие всех потоков на основе ввода-вывода .

Пожалуйста, обратите внимание, что закрытие уже закрытого потока приведет к Исключение IllegalStateException .

3. Заключение

В этом коротком уроке мы увидели различия между простыми потоками и тяжелыми потоками ввода-вывода. Мы также узнали, как эти различия влияют на наше решение о том, закрывать или нет потоки Java 8.

Как обычно, пример кода доступен на GitHub .