Java Reference
In-Depth Information
This would be a rather imperative implementation. From a functional standpoint, we actually want
three methods. Can you see where? The trick is to realize that walking the directory is a useful trick, and
there is no reason to couple that useful trick to the details about what to do when you get there. One helpful
approach to get to this functional decomposition is to be very strict about your method doing only one thing,
keeping in mind that the average Java developer's concept of “one thing” is probably far too broad: printing
all the lines of all the files in a directory is not one thing; walking the files in a directory is one thing—and
even that is a pretty big thing. It helps if you first do a back-of-the-napkin style of design before you write any
code. If it takes more than one line to describe a function, you're doing something wrong. One functional
decomposition of the problem looks like this:
Given:
[Lines] File
file
=> Stream<Result<String>>
Return all the lines in
file
[Walk] File
dir
=> Stream<File>
For each file in
dir
, return the file in the stream
Implement:
File
dir
=> Stream<Result<String>>
Apply
dir
to
Walk
and flatMap the result to
Lines
Those are the three functions we will implement now. Since all three of them take a
File
as the first
argument, we will implement them on a subclass of
File
, which we will call
FunFile
. (This class is available
in the
also
project, should you want to see it in production.)
Let's begin with the
Lines
method, since it will be the simplest. The
java.nio.file.Files
class,
introduced in Java 7, has been extended with a number of useful
Stream
-generating static methods,
including
Files.lines
. Unfortunately, despite the name, the
Files
class acts mostly on
Path
instances.
Furthermore, the API produces a
Stream
of
String
objects and throws an
IOException
. This method
signature makes the method ill-suited for working within streams, but its core functionality is exactly what
we want: we just need to make the API match what we are looking for. The implementation of our wrapper is
given in Listing 4-8. Keep in mind that this is attached to a class extending
java.io.File
.
Listing 4-8.
FunFile.getLines()
/**
* If this file is a plain file, provides the lines of the file (read as
* UTF-8) without the line-termination characters as a stream.
* Otherwise, returns a single {@link Result} instance with an
* {@link IOException}.
*
* @return The lines of the file; never {@code null}
*/
public Stream<Result<String>> getLines() {
if (!isFile()) {
return Stream.of(new Result<>(new IOException(
"File is not a plain file: " + toString()
)));
}
try {
return Files.lines(toPath()).map(Result::new);
} catch (IOException ioe) {
return Stream.of(new Result<>(ioe));
}
}