Quick, what's your standard idiom for closing Closeable resources in Java? Is it this?
OutputStream stream = new FileOutputStream(file);
try {
writeMessage(stream);
} finally {
stream.close();
}
I promise this isn't about a lack of a try/catch around the close() invocation. I'd rather clutter the prose with a message to ignore it (ignore it) than clutter the code sample. No, this post is about safely closing resources using generics, an anonymous class, and almost twice as many characters... sweet, an orgy of complexity!
withClose(
new FileOutputStream(file),
new Action<OutputStream>(){
public void call(OutputStream stream) throws IOException {
writeMessage(stream);
}
});
Just because Java makes it hard doesn't mean it isn't the right thing to do. Consider this, what stops you from forgetting the close() on a resource? A unit test? A code review? A pairing partner? Or just an amazing attention to detail? Do you think your method is more or less reliable than the javac compiler?
Let's examine what's going on in the above examples. The first one is simple. You create a stream, write some data into it, and then close it. The second does the same thing slightly differently. You create a stream, and then create a little function that writes some data into a stream. And then you pass them both to a method that will presumably call your function back, passing you the stream, and then safely closing it when it's all done.
The first one is simpler. There's no argument about that. But here's why I prefer the second one:
- Structure over Convention - Uncle Bob Martin's latest book Clean Code recommends using structure over convention. Odd advice in a post-Rails world, but there is value in having the design decision of closing resources in a single place, the withClose method. The point is that there is no way to incorrectly invoke withClose(). You either give it objects of the correct type of the compiler complains.
- Lean on the Compiler - Michael Feathers coined the term "Lean on the Compiler" in his book, Working Effectively with Legacy Code. I say, if we can get the compiler to enforce something for us then we should. Why use a statically typed language if that isn't a principle? Unfortunately for me, Michael Feathers meant something totally different in this book. He recommended finding usages of a variable by renaming the definitions and then analyzing the resulting compiler errors. Programmers seem to have no problem with co-opting terms and overloading them with meaning. I'm sticking with my new, invented meaning; let the confusion begin now.
- Dependency Injection - Another Bob Martin book, Agile Software Development, strongly advocates Dependency Injection and the Hollywood Principle. The idea is the movie producer refrain, "Don't call us, we'll call you." Interpreted here to mean, don't call our object to interact with it, give us a piece of code to execute and we'll give you what you need. Like dropping off your headshot at the casting agency, we're dropping off our Action function object at the withClose() service. You could further make this idiom more encapsulated by making the Action function part of the Closeable package and then making the methods on Closeable package-private, thus forcing usage of this idiom.
- Monady Goodness? - Man, those functional programming guys get all the cool words: catamorphisms, continuations, and monads. While there is certainly more to monads than just inversion of control, the basic usage is, as Brian Hurt says, "this code needs to execute in a time and place where condition X is true." In this case, the time when an OutputStream is open and usable.
The problem with this approach is the verboseness. It's not just creating the anonymous class, it's the fact that Java requires you to define a named function object with explicitly named types (including Exception types!). In this case, Action:
interface Action <T> {
void call(T input) throws IOException;
}
The
Functional Java library helps here by providing a unary function called
F1, but seriously, now many F1, F2, F3 types do you want in your codebase. I'd have more luck implementing these myself than convincing the gatekeepers to include Functional Java in the release. In fact, I did and no one noticed. Another problem is the generics of a withClose() function can appear daunting. 3 Ts and an extends? Sounds like my shopping list at Target for my daughter.
static <T extends Closeable> void withClose(T stream, Action<T> delegate) throws IOException {
try {
delegate.call(stream);
} finally {
stream.close();
}
}
I've memorized Josh Bloch's PECS (Producer Extends Consumer Super) as a mnemonic for remembering how to write wildcards and I still never write it correctly the first time. Arguably ever. Oh well, write it once and be done with it.
The last issue is combinatorics. How do you combine a withClose() action and a withOpen() action and a withFoo() action... Well, as static method invocations it would be darn hard. Java uses objects as the unit of composition, so you need to reference the withX() monad style method as an object not a global procedure. More objects means more verboseness. And combining them elegantly requires some sort of logical combination library, in the style of bin4j. How far down the functional programming road do you want to take your Java code? A functional language would "fix" all these problems:
- Function objects do not need to be named or declared
- The syntax for creating function objects is trivial
- The generic types can be inferred rather than declared
- And functions are the natural unit of composition, and object baggage can be discarded
Perhaps it's time to try a functional language?