1. Обзор
С введением лямбда-выражений в Java 8 стало возможным писать код более лаконичным и функциональным способом. Потоки и функциональные интерфейсы — сердце этого революционного изменения в платформе Java.
В этом кратком руководстве мы узнаем, должны ли мы явно закрывать потоки Java 8, рассматривая их с точки зрения ресурсов.
2. Закрытие потоков
Потоки Java 8 реализуют интерфейс AutoCloseable
:
public interface Stream<T> extends BaseStream<...> {
// omitted
}
public interface BaseStream<...> extends AutoCloseable {
// omitted
}
Проще говоря, мы должны думать о потоках как о ресурсах, которые мы можем одолжить и вернуть, когда закончим с ними. В отличие от большинства ресурсов, нам не нужно всегда закрывать потоки.
Поначалу это может показаться нелогичным, поэтому давайте посмотрим, когда мы должны и когда мы не должны закрывать потоки Java 8.
2.1. Коллекции, массивы и генераторы
В большинстве случаев мы создаем экземпляры Stream
из коллекций, массивов или функций-генераторов Java. Например, здесь мы работаем с коллекцией String
через Stream API:
List<String> 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<String> lines = Files.lines(Paths.get("/path/to/file"))) {
lines.flatMap(line -> Arrays.stream(line.split(","))) // omitted
}
Таким образом, компилятор автоматически закроет канал. Ключевым моментом здесь является закрытие всех потоков на основе ввода-вывода .
Обратите внимание, что закрытие уже закрытого потока вызовет исключение IllegalStateException
.
3. Заключение
В этом коротком руководстве мы увидели различия между простыми потоками и потоками с большим объемом операций ввода-вывода. Мы также узнали, как эти различия влияют на наше решение о том, следует ли закрывать потоки Java 8.
Как обычно, пример кода доступен на GitHub .