Imagine that we changed WordUsage to have a “segment number.” For the sonnets, this would be the
sonnet number. For the plays, this would be the act number, with preface material having segment number 0.
Someone implementing this would go into WordUsage, and he would update that class to use the segment
number throughout its code, and then all its tests would pass and the class would generally look great. Our
lambda code would compile and execute just fine, and all our tests would pass. But the resulting data would
be corrupt: for any given text, it would look like there were two different words at line offset 1, word offset 1.
Until someone analyzed and validated the data, there would be no way to detect that corruption. Once upon
a time, the code worked very well. Due to a change in the code over time, the code ceased to work properly.
That is code rot.
What we are seeing is the advantage of the object-oriented principle of encapsulation. The fact is that
the getters on our WordUsage instance are a violation of encapsulation in spirit, if not technically: they
directly expose implementation details about the class. It is possible for us to change those implementation
details sometime in the future, but doing so in any significant way—such as adding a new field and
redefining what an existing field means—invites code rot.
The lesson here is that lambdas are powerful things. Even more powerful than lambdas themselves,
however, is the ability for Java 8 to leverage object-oriented best practices (such as encapsulation) in
combination with functional behaviors (such as mapping stream elements). This is why postfunctional
languages are so incredibly powerful when used properly: it really does mix the best of both paradigms.
Now that we have converted our stream of WordUsage instances into a stream of tab-separated value
(TSV) strings, we need to print them out. Doing this is simple: we will get our hands on a PrintWriter or
PrintStream instance, and then have the stream execute in its println method for each element. In our
case, we will use System.out for our PrintStream instance, and output as the output stream of String
elements. In that case, the resulting code looks like this:
This demonstrates the use of a given instance's method as a lambda: we are literally taking the
particular instance of System.out , and the invocation of println on that particular instance, and converting
that into a lambda. That's a lot of lifting for a couple of colons, and that's the power of Java 8. With all of this
established, we can return to the start: downloading from the database.
Mapping the ResultSet to a Stream
The task of mapping the ResultSet instance to a Stream instance is not as trivial as we may perhaps like.
If we take a look at the Stream class, it is an interface. If it were a small, well-defined interface such as the
Iterator class, we could just implement a direct bridge. Unfortunately, it's a huge interface with some very
tricky (and ambiguous) definitions, so it is not something we are going to want to implement directly.
We will take a look at four ways of implementing our bridge from ResultSet to Stream:
Using Stream.Builder to construct a stream up front.
Using Stream.of and Stream.flatMap to construct the stream on demand.
Implementing the Stream built on Spliterators.AbstractSpliterator .
Mapping the ResultSet into an Iterator and then into a Spliterator and then
into a Stream .
Each of these four approaches has advantages and disadvantages, which we will see when we consider
their implementations. The result of all four approaches will be a Stream of WordUsage instances, and that
stream can get plugged immediately into all the work that we have done in the other sections of this chapter.