Java Reference
In-Depth Information
When programming in a functional style, it really pays to first stop and think about what you really want
to accomplish. First, let's consider the shape of the function that we want. We know that we want to act on a
java.io.File
instance, so we should either take that a file as input or make it as a method of something that
extends the
File
class. The output side is trickier. We do not know what a user is going to do with the lines
of the file. We could take a
Consumer
to process them, but it's both simpler and more flexible to return the
lines. We could read all the lines into some kind of
Collection
, but that requires all the lines to be read into
memory, and it prevents any processing from occurring until the lines are done being read. So it is best to
return a
Stream
, which provides flexibility and support for concurrent processing. But a stream of what?
The return value may not be as obvious at it initially seems. A
Stream
of
String
instances seems like the
obvious choice, but that is only kind of right. At any point in time, we could have an
IOException
thrown,
and when you are dealing with things like navigating directories, you should absolutely expect them.
Our pattern so far in this chapter has been to create two methods: one that accepts a
Consumer
to process
the errors, and one that returns
Result
objects. Although duplication of the API is made simple through
functional programming, it is certainly annoying and it clutters the class. Let's try for a different approach.
The new approach to processing the class will still use the
Result
class for elements in the
Stream
, but
we will give our users a new tool. We will provide a class that is constructed with a
Consumer
, and which
processes a Stream of Result instances to remove the exceptional cases. This gives the user the ability to
specify how to handle exceptions after the fact, and convert the
Stream
of
Result
s of
String
s into the much
more familiar
Stream
of
String
s. So now we have two things to write: the method that will take a
File
and
produce a
Stream
of
Result
s of
String
s, and a post-processor class that will turn a
Stream
of
Result
s of
String
s into a
Stream
of
String
s. Now it is time to start thinking about implementations, starting with the
post-processor.
Complex Stream Processing Using Creative Flattening
This is where we will really get into a fancy
Stream
stunt, and the real power of a
Stream
will come out.
The difficulty is that we have a fairly complex operation on a stream. We want to handle and then remove
elements from the stream meeting a certain criteria: in this case, we want to handle and remove
Result
instances that contain exceptions.
If you glance over the
Stream
API, it first seems like we want to perform a
map
operation: we want to take
in types of
Result<String>
and return a
String
, and the
map
method is how you transform the type if the
Stream
. The
map
method looks like this:
<R> Stream<R> map(Function<? super T,? extends R> mapper)
The problem is that we do not have a one-to-one mapping: not every input will produce an output.
We could return an
Option
, but what does that gain us? Why wouldn't we just stick with our
Result
type
instead? If we get fixated with the
map
method, we could then create a two-part system, first performing
the
map
and then performing the
filter
to remove elements. If we were to go down that point, we would
not be leveraging the type system to tell us that the
Result
has been handled, and we would be creating a
possibility for an error to creep in between when the
Result
is handled and where it is removed. A better
alternative is to use
flatMap
.
The
flatMap
method is based on the concept of “flattening,” which is a functional structure that takes in
a container of containers, and produces a single “flat” container with all the elements of all the containers.
Consider that you had a list that looked like this:
List<List<String>> list = Arrays.asList(
Arrays.asList("Eddard Stark", "Catelyn Tully", "Benjen Stark"),
Arrays.asList("Lysa Telly", "Jon Arryn", "Robin Arryn"),
Arrays.asList("Cersei Lannister", "Robert Baratheon")
);