
dtonhofer
Functional Programming in Java, Second Edition: p.65 "WatchFileChange.java"
Suggesting a text change:
We read:
We’ve registered a WatchService to observe any change to the current directory.
but what actually happens is
We’ve registered a path with a filesystem
WatchService
to get told about ‘modification’ changes to the current directory.
On my system, file deletion does not give rise to a notification, but creation does (probably because after creation, the file is additionally modified)
The text
"Report any file changed within next 1 minute..."
should really say
System.out.println("Report the first change on '" + path + "' within the next 1 minute...");
Note that the code given uses an inner loop. At this point, I really feel we should use collect()
:
if(watchKey != null) {
watchKey.pollEvents()
.stream()
.forEach(event ->
System.out.println(event.context()));
}
Nicer:
String res = (watchKey == null) ? "nothing happened at all!" :
watchKey.pollEvents()
.stream()
.map(event -> event.context().toString())
.collect(Collectors.joining(", "));
As I had some trouble understanding how the WatchService actually works and what those keys are doing, here is the full method
public void watchFileChange() throws IOException {
final Path path = Paths.get(theDir);
String res;
// Try-with-resources to close the WatchService at the end
// (and thus cancel all the WatchKeys registered with it)
try (WatchService watchService = path.getFileSystem().newWatchService()) {
try {
// No need to retain the WatchKey returned by path.register()
path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Report the first change on '" + path + "' within the next 1 minute...");
WatchKey watchKey = null;
// poll() "Retrieves and removes the next watch key, waiting if necessary up to
// the specified wait time if none are yet present."
try {
watchKey = watchService.poll(1, TimeUnit.MINUTES);
} catch (InterruptedException ex) {
System.out.println("Got interrupted");
}
res = (watchKey == null) ? "nothing happened at all!" :
watchKey.pollEvents()
.stream()
.map(event -> event.context().toString())
.collect(Collectors.joining(", "));
} catch (NoSuchFileException ex) {
res = "Looks like there is no filesystem entry '" + path + "'";
}
}
System.out.println(res);
}
First Post!

dtonhofer
I can no longer edit this post (why!), so here is the latest version of my file “WatchFileChanges.java”
It contains a modified book version and one which uses Optional
(an interesting modification that has to be attempted with an IDE to get unexpected lessons in type inference! )
package chapter3;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.*;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
// ---
// Described in "Functional Programming in Java" on page 64.
// Derived from the original "compare/fpij/WatchFileChange.java".
// This file is "src/test/java/chapter3/WatchFileChanges.java"
// Instead of having a main() we code it into a JUnit test case.
// ---
public class WatchFileChanges {
private final static String where = "/home/aloy";
private final static Path thePath = Paths.get(where);
private String stringAddClass(final Object o) {
assert o != null;
return o.getClass().getName() + ": " + o;
}
// As in the book
@Test
public void watchFileChange() throws IOException, InterruptedException {
String txt; // cannot be made final
// Try-with-resources to close the WatchService at the end
// (and thus cancel all the WatchKeys registered with it)
try (final WatchService watchService = thePath.getFileSystem().newWatchService()) {
try {
// No need to retain the WatchKey returned by path.register()
thePath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Report the first change on '" + thePath + "' within the next 1 minute...");
// poll() "retrieves and removes the next watch key", i.e. returns with a WatchKey once a
// change has been detected. Otherwise, it times out returning null.
// Note that we do NOT catch the InterruptedException, but leave it up the stack.
// If someone interrupted us, there must be reasons.
WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);
if (watchKey == null) {
txt = "Nothing happened at all, the WatchKey is null!";
} else {
txt = "Changes in: " +
watchKey.pollEvents()
.stream()
.map(event -> event.context().toString()) // MAKE STRING!
.collect(Collectors.joining(", "));
}
} catch (NoSuchFileException ex) {
txt = "Looks like there is no filesystem entry '" + thePath + "'";
}
}
System.out.println(txt);
}
// More interesting, using Optional<>
@Test
void watchingAFileChangeFirstMoreInteresting() throws IOException, InterruptedException {
final String txt;
try (final WatchService watchService = thePath.getFileSystem().newWatchService()) {
thePath.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
System.out.println("Report the first change on '" + thePath + "' within the next 1 minute...");
// poll() "retrieves and removes the next watch key", i.e. returns with a WatchKey once a
// change has been detected. Otherwise, it times out returning null.
// Note that we do NOT catch the InterruptedException, but leave it up the stack.
// If someone interrupted us, there must be reasons.
final WatchKey watchKey = watchService.poll(1, TimeUnit.MINUTES);
if (watchKey == null) {
txt = "Nothing happened at all, the WatchKey is (null)!";
} else {
// The typing here is special as WatchEvent<?> may be parametrized by different actual types
{
Stream<WatchEvent<?>> watchKeyStream = watchKey.pollEvents().stream();
Stream<Object> contextStream = watchKeyStream.map(WatchEvent::context);
Stream<Optional<Object>> optContextStream = contextStream.map(Optional::of);
List<Optional<Object>> list = optContextStream.toList();
// The map() makes a string including the class name of
// whatever the "Stream<Optional<Object>>" gave us
txt = "Changes in: " +
list.stream()
.map(opt -> opt.map(o -> stringAddClass(o)).orElse("(null)"))
.collect(Collectors.joining(", "));
}
/* This does not pass typecheck:
{
List<Optional<Object>> list = watchKey.pollEvents().stream()
.map(WatchEvent::context)
.map(Optional::of)
.toList();
String txt = list.stream()
.map(opt -> opt.map(o -> o.getClass().getName() + ": " + o).orElse("(null)"))
.collect(Collectors.joining("\n"));
System.out.println(txt);
}
*/
}
}
System.out.println(txt);
}
}
Popular Pragmatic topics










Other popular topics









Latest in PragProg
Latest (all)
Categories:
Popular Portals
- /elixir
- /rust
- /wasm
- /ruby
- /erlang
- /phoenix
- /keyboards
- /js
- /rails
- /python
- /security
- /go
- /swift
- /vim
- /clojure
- /java
- /haskell
- /emacs
- /svelte
- /onivim
- /typescript
- /crystal
- /c-plus-plus
- /tailwind
- /kotlin
- /gleam
- /react
- /flutter
- /elm
- /ocaml
- /vscode
- /opensuse
- /ash
- /centos
- /php
- /deepseek
- /scala
- /zig
- /html
- /debian
- /nixos
- /lisp
- /agda
- /sublime-text
- /textmate
- /react-native
- /kubuntu
- /arch-linux
- /revery
- /ubuntu
- /manjaro
- /spring
- /django
- /diversity
- /nodejs
- /lua
- /slackware
- /julia
- /c
- /neovim