23

Consider the following code:

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

Does a terminal operation (like forEach) close the underlying file that has been opened?

Refer to the relevant parts of the javadoc of Files.list:

The returned stream encapsulates a DirectoryStream. If timely disposal of file system resources is required, the try-with-resources construct should be used to ensure that the stream's close method is invoked after the stream operations are completed.

If it doesn't call Stream.close(), what would then be the best alternative to call it while producing maintainable code?

skiwi
  • 66,971
  • 31
  • 131
  • 216
  • 1
    This question is similar to http://stackoverflow.com/questions/26997240/do-terminal-operations-close-the-stream?rq=1 with the exception of also asking what "counter-measures" to take. – TheConstructor Dec 09 '14 at 15:04
  • 2
    Also similar to [this question](http://stackoverflow.com/questions/22921623/closing-streams-in-the-middle-of-pipelines), which has a long answer describing the motivation for this behaviour. – Lii Dec 10 '14 at 14:14
  • Strongly disagree with the delete vote. It's useful for this question to exist closed, as a pointer to the canonical answer. – amalloy Feb 27 '20 at 00:18

2 Answers2

28

Terminal operators do NOT close the stream automatically. Consider this code:

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

This does NOT print "Closed".

However, the following does print "Closed":

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

So the best way to do it is to use the try-with-resources mechanism.

David ten Hove
  • 2,748
  • 18
  • 33
1

So a quick-check reveals that forEach does not close the 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();
        }
    }
}

I see the two presented mitigations:

  1. use try-with-resources as stated in the Files.list JavaDoc
  2. write your own helper-method which utilizes a finally-block

Which is more maintainable depends probably on how-many helper-methods you would need.

TheConstructor
  • 4,285
  • 1
  • 31
  • 52