try-with-resources and null resource (original) (raw)

Ian Clough ian.clough at oracle.com
Thu Jan 27 01:32:48 PST 2011


On 27/01/2011 09:10, Stephen Colebourne wrote:

On 26 January 2011 18:05, Tim Peierls<tim at peierls.net> wrote:

No, I am writing about what I think the semantics of the construct should be. I'm drawing an analogy between two constructs, one old, one new, that many users will see as similar, rightly or wrongly. Their expectations for the new construct's semantics will be shaped by this perception. I claim that most users won't expect NPE to be thrown immediately if the initializer is null, and that they will expect NPE to be thrown on the first attempt to dereference a null. They'll expect this because that's what happens in the analogous case.

You can say the analogy is false, you can say that people should approach this construct as being radically different from try-finally, you can even say that most people are sloppy thinkers. But you can't change how they think by wishing, and it would be wrong to design the feature without taking such people into account. Funnily enough, I have proposed similar analogies in the past. But they are seductively dangerous. How many devs looking at a foreach loop know it uses an iterator? Or an index if its an array? The same applies here. And how many have tried to use an Iterator (or even Enumeration) in a foreach loop instead of an Iterable. In fact allowing an Iterator would be very useful as it would allow a non-default iterator (e.g a filtering Iterator) to be used without having to use an artificial intermediate container.

Also you get the same exception if the Iterable itself is null or if the Iterator returned from calling iterator is null which confusing.

A similar case is closures/lambdas. There, the analogy is to inner classes, yet after much thought, Oracle has come to the same conclusion I did - that "this" within a lambda should not act as "this" does in an inner class. Sometimes, we have to break with the analogy to get the right result.

Anyway, a real use case is far more helpful: Consider getResourceAsStream() which returns null if the resource does not exist: try (InputStream stream = getClass().getResourceAsStream("/com/foo/mightOrMightNotExist")) { if (stream != null) { readStream(stream); } } This will fail ATM, because of that null, yet the code is perfectly clear and readable. What's the alternative? InputStream stream = getClass().getResourceAsStream("/com/foo/mightOrMightNotExist"); if (stream != null) { try (InputStream stream2 = stream) { readStream(stream2); } } That now works, but is significantly less clear to read, more verbose and more prone to accidental refactoring. In fact, there was a bug when I first typed this email, as I wrote readStream(stream) rather than readStream(stream2). In this case, it would have no bad effects, but other similar cases might. In fact, I'd probably still write: InputStream stream = getClass().getResourceAsStream("/com/foo/mightOrMightNotExist"); try { if (stream != null) { readStream(stream); } finally { org.apache.commons.lang.IOUtils.closeQuietly(stream); } } as it is clearer than the t-w-r version.

Executive summary: C# has got it right. Null resources should be silently be accepted. Priority. Initially I said this didn't matter much to me. With this new use case (straight out of my day job yesterday), I really care about my conclusion now. Please reconsider the semantics based on this use case. Stephen



More information about the coin-dev mailing list