• Tidak ada hasil yang ditemukan

Watching a File Change

Dalam dokumen Book Functional Programming in Java (Halaman 75-78)

We can use flatMap() for various operations, but the problem on hand nicely demonstrates the usefulness of this method. Each subdirectory has a list or stream of files and we’re trying to get a combined (or flattened) list of files in all the subdirectories of the current directory.

Some directories (or files) may be empty and may not have children. In that case, we simply wrap a stream around the no-child directory or file element.

In case we choose to ignore a file, the flatMap() method in the JDK can deal with empty quite well; it will merge a null reference to a Stream as an empty collection. Let’s see the flatMap() method in action.

compare/fpij/ListSubDirs.java

public static void betterWay() { List<File> files =

Stream.of(new File(".").listFiles())

.flatMap(file -> file.listFiles() == null ? Stream.of(file) : Stream.of(file.listFiles())) .collect(toList());

System.out.println("Count: " + files.size());

}

We obtained a stream of files in the current directory and invoked the flatMap() method on it. To this method we passed a lambda expression that returns a Stream of children for the given file. The flatMap() returns a flattened map of a collection of all the children of the current directory’s subdirectories. We collect those back into a List using the toList() methods of the collect() and Collectors. The lambda expression we passed as a parameter to the flatMap() method returned, for a given file, a Stream of its children (if any). Otherwise, it returned a stream with just the file. The flatMap() method gracefully handled that and mapped these streams into a resulting collection of streams and finally flat- tened it into one final Stream of Files.

flatMap() eliminates so much effort—it nicely combines a sequence of two operations, often called as a monadic composition—into one single elegant step.

We saw how the flatMap() method simplifies the task of listing the immediate (one level deep) contents of a subdirectory. Next we’ll create an observer for file changes.

and system resources. Here we’ll explore the facility that’s been available since Java 7, the WatchService, to watch for file changes. Most of the features we’ll see here are from JDK 7 and the main improvement will be in the convenience of the internal iterator.

Let’s create an example to watch for file changes in the current directory. The Path class in the JDK can lead to an instance of the file system, which acts as a factory for the watch service. We can register with this service for any noti- fication, like so:

compare/fpij/WatchFileChange.java final Path path = Paths.get(".");

final WatchService watchService = path.getFileSystem()

.newWatchService();

path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

System.out.println("Report any file changed within next 1 minute...");

We’ve registered a WatchService to observe any change to the current directory.

We can poll the watch service for any change to files in this directory, and it will notify us through a WatchKey. Once we gain access to the key, we can iterate though all the events to get the details of the file update. Since multiple files may change at once, a poll may return a collection of events rather than a single event. Let’s look at the code for polling and iterating.

compare/fpij/WatchFileChange.java

final WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);

if(watchKey != null) { watchKey.pollEvents()

.stream()

.forEach(event ->

System.out.println(event.context()));

}

We see an interplay of Java 7 and Java 8 features here. We transformed the collection returned by pollEvents() into a Java 8 Stream and then used an internal iterator on it to display the details of the updated file(s).

Let’s run the code, change the sample.txt file in the current directory, and see if the program tattletales about the change.

Report any file changed within next 1 minute...

sample.txt

When we modified the file, the program promptly reported that the file was changed. We can use this facility to watch for changes to various files and Watching a File Change

61

perform appropriate tasks in our applications. Or we could register for only file creation or deletion, as we desire.

Recap

The regular tasks of working with strings and files and creating custom comparators got a lot easier and more concise with lambda expressions and method references. Anonymous inner classes morphed into an elegant style and, along the way, mutability disappeared like the fog in the morning sun.

As a bonus for favoring this style, we can benefit from the new JDK facilities to iterate efficiently over large directories.

Now you know how to create lambda expressions to pass as parameters to methods. In the next chapter we’ll look at ways to design programs with functional interfaces and lambda expressions.

CHAPTER 4

Design is not just what it looks like and feels like. Design is how it works.

Steve Jobs

Designing with Lambda Expressions

OOP has become the de facto standard, but with lambda expressions in Java, we can pull a few more techniques out of our bag of design tricks. In Java, OOP and functional style can now complement each other and can nicely interplay. We can use these to create lightweight designs that are flexible to change and easier to extend.

We can replace interfaces, class hierarchies, and anonymous inner classes with concise code. We need fewer lines of code to get the same job done, and we can quickly try out new ideas.

In this chapter lambda expressions bring to life some neat design ideas; where we often use objects, we’ll instead use lightweight functions. We’ll use lambda expressions to easily separate logic from functions, making them more extensible. Then we’ll apply them to delegate responsibilities and implement the decorator pattern in just a couple of lines of code. Finally we’ll use them to turn mundane interfaces into fluent, intuitive interfaces.

Dalam dokumen Book Functional Programming in Java (Halaman 75-78)