Закрывают ли терминальные операции над потоками источник?

Рассмотрим следующий код:

Path directory = Paths.get(/* some directory */);
Files.list(directory).forEach(System.out::println);

Закрывает ли терминальная операция (например, forEach) базовый файл, который был открыт?

См. соответствующие части javadoc Files.list:

Возвращенный поток инкапсулирует DirectoryStream. Если требуется своевременная утилизация ресурсов файловой системы, следует использовать конструкцию try-with-resources, чтобы гарантировать вызов метода закрытия потока после завершения операций потока.

Если он не вызывает Stream.close(), что тогда будет лучшей альтернативой для его вызова при создании поддерживаемого кода?


person skiwi    schedule 09.12.2014    source источник
comment
Этот вопрос похож на stackoverflow .com/questions/26997240/, за исключением вопроса о том, какие контрмеры предпринять.   -  person TheConstructor    schedule 09.12.2014
comment
Также похоже на этот вопрос, на который есть длинный ответ, описывающий мотивацию для этого поведения.   -  person Lii    schedule 10.12.2014
comment
Категорически не согласен с голосованием об удалении. Полезно, чтобы этот вопрос существовал закрытым, как указатель на канонический ответ.   -  person amalloy    schedule 27.02.2020


Ответы (2)


Операторы терминала НЕ закрывают поток автоматически. Рассмотрим этот код:

Stream<Path> list = Files.list(directory).onClose(() -> System.out.println("Closed"));
list.forEach(System.out::println);

Это НЕ печатает «Закрыто».

Однако следующее печатает «Закрыто»:

try (Stream<Path> list = Files.list(directory).onClose(() -> System.out.println("Closed"))) {
    list.forEach(System.out::println);
}

Поэтому лучший способ сделать это — использовать механизм try-with-resources.

person David ten Hove    schedule 09.12.2014

Итак, быстрая проверка показывает, что forEach не закрывает DirectoryStream:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import java.util.stream.Stream;

/**
 * Created for http://stackoverflow.com/q/27381329/1266906
 */
public class FileList {
    public static void main(String[] args) {
        Path directory = Paths.get("C:\\");
        try {
            Stream<Path> list = Files.list(directory).onClose(() -> System.out.println("Close called"));
            list.forEach(System.out::println);
            // Next Line throws "java.lang.IllegalStateException: stream has already been operated upon or closed" even though "Close called" was not printed
            list.forEach(System.out::println);
        } catch (IOException | IllegalStateException e) {
            e.printStackTrace();  // TODO: implement catch
        }

        // The mentioned try-with-resources construct
        try (Stream<Path> list = Files.list(directory)) {
            list.forEach(System.out::println);
        } catch (IOException | IllegalStateException e) {
            e.printStackTrace();  // TODO: implement catch
        }

        // Own helper-method
        try {
            forEachThenClose(Files.list(directory), System.out::println);
        } catch (IOException | IllegalStateException e) {
            e.printStackTrace();  // TODO: implement catch
        }
    }

    public static <T> void forEachThenClose(Stream<T> list, Consumer<T> action) {
        try {
            list.forEach(action);
        } finally {
            list.close();
        }
    }
}

Я вижу два представленных смягчения:

  1. используйте попытку с ресурсами, как указано в Files.list JavaDoc
  2. напишите свой собственный вспомогательный метод, который использует блок finally

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

person TheConstructor    schedule 09.12.2014