Java functional programming of eleven: traversing a directory

  • 2020-04-01 03:30:41
  • OfStack

Lists the files in the directory

The list() method of the File class makes it easy to list the names of all the files in the directory. If you want to get a file and not just a file name, you can use its listFiles() method. That's easy. The hard part is what to do with the returned list. Instead of using traditional verbose external iterators, we used elegant functions to actually iterate over the list. Here we also use the JDK's new CloseableStream interface and some associated higher-order functions.

The following code lists the names of all the files in the current directory.


Files.list(Paths.get("."))
     .forEach(System.out::println);

If you want to list other directories, replace ". "with the full path to the directory you want to access.

We first used the get() method of Paths to create a Path instance with a string. Then we get a ClosableStream object through the list() method of the Files utility class, which we can use to traverse all the Files in the directory. We then use the internal iterator forEach() to print out the file name. Let's look at some of the output of this code: listing the files and subdirectories in the current directory.


./aSampleFiles.txt
./bin
./fpij
...

If we only want to get subdirectories of the current directory instead of files, we can use the filter() method:


Files.list(Paths.get("."))
     .filter(Files::isDirectory)
     .forEach(System.out::println);

The ilter() method filters the directory from the file stream. Instead of passing a lambda expression, we passed in a reference to the isDirectory method of the Files class. Recall that the filter() method requires a Predicate type that returns a Boolean value, which is just right. Finally we use an internal iterator to print out the name of the directory. The program will print out a subdirectory of the current directory.


./bin
./fpij
./output
...

This is much easier to write and saves a lot of code than the old Java way. Let's look at how to list files that match a pattern.

Lists the files specified in the directory

Java has long provided a variant of the list () method to filter file names. This version of the list() method takes a parameter of type FilenameFilter. This interface has only one accept() method, which takes two parameters: File dir(for directory) and String name(for File name). The accept() method returns true and the file name appears in the returned list, not false. So let's implement this method.

It is customary to pass an instance of an anonymous inner class that implements the FilenameFilter interface to the list() method. For example, let's look at how to return.java files in the fpij directory in this way.


final String[] files =
new File("fpij").list(new java.io.FilenameFilter() {
public boolean accept(final File dir, final String name) {
return name.endsWith(".java");
}
});
System.out.println(files);

It takes a lot of work to write a few lines of code. This code is too noisy: create objects, call functions, define anonymous inner classes, embed methods in classes, and so on. We don't have to endure this anymore, just pass in a lambda expression that takes two arguments and returns bollean. The Java compiler takes care of the rest.

The previous example could simply replace the anonymous interior with a lambda expression, but there is room for further optimization. The new DirectoryStream tool can help us traverse large directory structures more efficiently. So let's try this. This is a variant of the newDirectoryStream() method, which accepts an additional filter.


Files.newDirectoryStream(
      Paths.get("fpij"), path -> path.toString().endsWith(".java"))
     .forEach(System.out::println);

In this way we get rid of the anonymous inner classes and make the tedious code concise. The output from both versions is the same. Let's print the specified file.

This code will only output the.java file in the specified directory.


fpij/Compare.java
fpij/IterateString.java
fpij/ListDirs.java
...

We filter files based on file names, but we can also easily filter by file attributes, such as whether the file is executable, readable, writable, etc. Doing so requires a listFiles() method that takes a parameter of type FileFilter. We still use lambda expressions to implement instead of creating anonymous inner classes. Now let's look at an example that lists all the hidden files in the current directory.


final File[] files = new File(".").listFiles(file -> file.isHidden());

If we are dealing with a large directory, we can use DirectoryStream instead of calling the method above File directly.

The signature of the lambda expression we passed to the listFiles() method is the same as the signature of the accept() method on the FileFilter interface. This lambda expression takes arguments to a File instance, in this case a File. Just returned true if the file is a hidden property, false otherwise.

We can actually simplify the code a little bit here. Instead of passing a lambda expression, we can pass a method reference to make the code look cleaner:


new File(".").listFiles(File::isHidden);

We implemented it first with a lambda expression and then with a method reference to make it more concise. If we were to write new code, it would be in this neat way. If we can discover this compact implementation early, we should definitely use it first. There's a saying that goes, "make it work, then make it better." you make the code work, and then you make it better.

We used an example to filter out the specified file from the directory. Let's take a look at how to walk through subdirectories in a given directory.


Related articles: