1. 概要

In this quick tutorial, we’ll learn different ways to list files within a directory.

2. リスト

We can list all the files in a directory with the listFiles() method on the java.io.File object that refers to a directory:

public Set<String> listFilesUsingJavaIO(String dir) {
    return Stream.of(new File(dir).listFiles())
      .filter(file -> !file.isDirectory())
      .map(File::getName)
      .collect(Collectors.toSet());
}

As we can see, listFiles() returns an array of File objects that are the contents of the directory.

We’ll create a stream from that array. Then we’ll filter away all the values that aren’t subdirectories. Finally, we’ll collect the result into a set.

Note that we chose the Set type over List. In fact, there’s no guarantee of the order in which the files are returned by listFiles().

Using the listFiles() method on the newly instantiated File warrants some caution, as it might be null. This happens when the provided directory isn’t valid. As a result, it’ll throw a NullPointerException:

assertThrows(NullPointerException.class,
        () -> listFiles.listFilesUsingJavaIO(INVALID_DIRECTORY));

Another disadvantage of using listFiles() is that it reads the whole directory at once. Consequently, it can be problematic for folders with a large number of files.

So let’s discuss an alternate way.

3. DirectoryStream

Java 7 introduced an alternative to listFiles called DirectoryStream. A directory stream was created to work well with the for-each construct to iterate over a directory’s content. This means that, instead of reading everything at once, we iterate over the contents of the directory.

Let’s use this to list the files of a directory:

public Set<String> listFilesUsingDirectoryStream(String dir) throws IOException {
    Set<String> fileSet = new HashSet<>();
    try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(dir))) {
        for (Path path : stream) {
            if (!Files.isDirectory(path)) {
                fileSet.add(path.getFileName()
                    .toString());
            }
        }
    }
    return fileSet;
}

Above, we let Java handle the closing of the DirectoryStream resource through the try-with-resources construct. Similarly, we return a set of files in the folder filtering away directories.

Despite the confusing name, DirectoryStream isn’t part of the Stream API.

Now we’ll see how to list files using the Stream API.

4. Java8でのリスト

Java 8 introduced a new list() method in java.nio.file.Files. The list method returns a lazily populated Stream of entries in the directory.

4.1. Using Files.list()

簡単な例を見てみましょう。

public Set<String> listFilesUsingFilesList(String dir) throws IOException {
    try (Stream<Path> stream = Files.list(Paths.get(dir))) {
        return stream
          .filter(file -> !Files.isDirectory(file))
          .map(Path::getFileName)
          .map(Path::toString)
          .collect(Collectors.toSet());
    }
}

Similarly, we return a set of files contained in the folder. Although this might look similar to listFiles(), it’s different in how we get the files’ Path.

Here, the list() method returns a Stream object that lazily populates a directory’s entries. As a result, we can process large folders more efficiently.

Again, we created the stream using the try-with-resources construct to ensure that the directory resource is closed after reading the stream.

4.2. Comparison With File.list()

We mustn’t confuse the list() method provided by the Files class with the list() method on the File object. The latter returns a String array of names of all the entries of the directory, both files, and directories.

5. ウォーキング

Other than listing files, we might want to traverse the directory to one or more levels deeper than its direct file entries. In that case, we can use walk():

public Set<String> listFilesUsingFileWalk(String dir, int depth) throws IOException {
    try (Stream<Path> stream = Files.walk(Paths.get(dir), depth)) {
        return stream
          .filter(file -> !Files.isDirectory(file))
          .map(Path::getFileName)
          .map(Path::toString)
          .collect(Collectors.toSet());
    }
}

The walk() method traverses the directory at the depth provided as its argument. Here, we traversed the file tree and collected the names of all the files into a Set.

Additionally, we might want to take some action as we iterate on each file. In that case, we can use the walkFileTree() method by providing a visitor describing the action we want to take:

public Set<String> listFilesUsingFileWalkAndVisitor(String dir) throws IOException {
    Set<String> fileList = new HashSet<>();
    Files.walkFileTree(Paths.get(dir), new SimpleFileVisitor<Path>() {
        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            if (!Files.isDirectory(file)) {
                fileList.add(file.getFileName().toString());
            }
            return FileVisitResult.CONTINUE;
        }
    });
    return fileList;
}

This method comes in handy when we want to do additional reading, moving, or deleting files as we go.

The walk() and walkFileTree() methods won’t throw a NullPointerException if we try to pass in a valid file instead of a directory. In fact, the Stream guarantees to return at least one element, the provided file itself:

Set<String> expectedFileSet = Collections.singleton("test.xml");
String filePathString = "src/test/resources/listFilesUnitTestFolder/test.xml";
assertEquals(expectedFileSet, listFiles.listFilesUsingFileWalk(filePathString, DEPTH));

6. 結論

In this brief article, we explored different ways to list files within a directory.

First, we used listFiles() to get all the contents of the folder. Then we used DirectoryStream to lazy load the directory’s content. We also used the list() method introduced with Java 8.

Finally, we demonstrated the walk() and walkFileTree() methods for working with the file tree.

いつものように、例の完全なソースコードは、GitHubから入手できます。