Java Reference
In-Depth Information
The stack trace in Listing 7-3 is not nearly as nice as the version in Listing 7-2. We still have the lambda
references in our stack trace, but they are references to where the lambda is declared; there is no reference
at all to the main method, which is where our lambda is wired into the stream. The code goes straight from
rather generic stream infrastructure to the place in the source where the lambda was declared. This can
make it tricky to figure out where the code is actually called; imagine if the lambda was defined in some
other class and reused in multiple streams - from experience, that exception would be very tricky to debug
based on the stack trace.
The other thing to note is that we have gotten well into our processing before this failed: we exploded on the
304th execution. That means we had 303 executions that completed successfully. Some of those were printed
out. Some of those, however, may not have been; it depends on how far down the pipeline they got when the
explosion occurred. Worse, we did not even try any of the numbers after we saw the explosion: a single explosion
will terminate the entire stream. So we have some of our elements in a completed state, some in an intermediate
state, and some that were never even initiated. The explosion just left things in an ugly place.
The final issue with this approach is that we have reintroduced exception swallowing, although we a
very sneaky about it. The good news is that we will always get an exception. However, if multiple threads
have exceptions, then we will only see the first. It is possible that multiple threads threw exceptions
simultaneously: for instance, if they were all doing database queries when the database went down. Yet, in
that case, you will only see the first exception, even though there were multiple problems.
Sometimes, these limitations are okay. Sometimes, your work really is all-or-nothing, or it is safe to
rerun multiple times, even if things were in indeterminate states. In these cases, the mapping of exceptions
to unchecked exceptions works just fine. However, if you want to try to salvage work from intermittent errors,
then you are going to need something more clever.
Handling Resources with an Exception-Handling Caller
Since Java 8 is a post-functional language, we can apply an object-oriented solution to our problem.
The object that we want will encapsulate the try-with-resources logic and the error handling, so that the user
can provide just the desired implementation. The object can then be queried after the map has run to deal
with any exceptions that arise.
The idea here is that we will have some code up front and some code at the end, and an opening for
user-defined code in the middle. My friend, mentor, and co-blogger, Brian Hurt, refers to this as the “hole
in the middle pattern.” Spring calls this “inversion of control,” and it was one of the key strengths of the
early Spring APIs, most especially Spring JDBC. In our case, the code up front is the creation of the resource
(the “try” part of the try-with-resources block) and the exception handling (the “catch” part of the try-with-
resources block). The opening for user-defined code in the middle is the code that takes the resource and the
stream value and returns the mapped value for the stream.
For the implementation, we will create this class along with two single-method interfaces: one that
defines how to make a resource, and one that defines how to process the resource. We will intend for the
user to implement these interfaces using lambdas. These interface methods will allow the user to throw
checked exceptions, so the user is totally free to do what they would like. The class itself will encapsulate the
creation of the resource and the handling of return values and exceptions.
There is a catch to this very neat theory: we have to return something in the case of an exception.
A legacy Java developer is again going to reach for null out of habit, but this is a bad habit. Java 8 provides
the Optional class, which represents a value that may or may not be set. This is a better option, because the
Optional class cannot lead you to accidentally raise a NullPointerException ; the API provides many better
alternatives for getting the value, including representing the common get-and-if-null-explode code pattern.
The result of our stream will therefore be an Optional container of the type that the map itself would
normally return. We can filter out empty Optional types using the filter method for streams, which makes
it very easy to clean up the streams from any exceptional elements. The empty elements do provide some
value, too: they enable us to discover that an error occurred in our stream, so we could provide somewhat
more intelligent behaviors.
Search WWH ::

Custom Search