try-with-resources and null resource

Rémi Forax forax at univ-mlv.fr
Tue Jan 25 09:40:47 PST 2011


On 01/25/2011 10:02 AM, Bruce Chapman wrote:
> On 25/01/2011 12:40 p.m., Rémi Forax wrote:
>> On 01/24/2011 09:00 PM, Joe Darcy wrote:
>>> Rémi Forax wrote:
>>>> On 01/24/2011 08:00 PM, Joe Darcy wrote:
>>>>> On 1/22/2011 5:09 AM, Rémi Forax wrote:
>>>>>> On 01/21/2011 08:38 PM, Joe Darcy wrote:
>>>>>>> Rémi Forax wrote:
>>>>>>>> I think try-with-resources should do a null check before entering
>>>>>>>> in the try block.
>>>>>>>>
>>>>>>>> 4. try(AutoCloseable c = null) {
>>>>>>>> 5.     // nothing
>>>>>>>> 6. }
>>>>>>>>
>>>>>>>> $ java TryWithresourceNPE
>>>>>>>> Exception in thread "main" java.lang.NullPointerException
>>>>>>>>        at TryWithresourceNPE.main(TryWithresourceNPE.java:6)
>>>>>>>>
>>>>>>>> I got a NPE from the ends of the try block, I think it will be
>>>>>>>> better to detect the case
>>>>>>>> before entering in the try block like foreach or switch does.
>>>>>>>>
>>>>>>>> And with this code:
>>>>>>>>
>>>>>>>> 5. try(InputStream i = null) {
>>>>>>>> 6.       i.available();
>>>>>>>> 7. }
>>>>>>>>
>>>>>>>> I got the exception below which is not understandable if you
>>>>>>>> don't know
>>>>>>>> how try-with-resources is translated.
>>>>>>>>
>>>>>>>> $ java TryWithresourceNPE
>>>>>>>> Exception in thread "main" java.lang.NullPointerException
>>>>>>>>        at TryWithresourceNPE.main(TryWithresourceNPE.java:6)
>>>>>>>>        Suppressed: java.lang.NullPointerException
>>>>>>>>            at TryWithresourceNPE.main(TryWithresourceNPE.java:7)
>>>>>>> I'm not too concerned about that stacktrace since the primary
>>>>>>> exception points to the right location.
>>>>>> But the suppressed exception occurs in a generated code that the
>>>>>> user don't write.
>>>>> But that is the whole point of try-with-resources, to allow users to
>>>>> not have to write the messy and error-prone clean-up code.
>>>> I have no problem if the error come from the close() itself,
>>>> the stacktrace will be explicit in that case.
>>>>
>>>> But here it comes because the spec allows try(...) to be used with null,
>>>> oh no wait! the generated finally block doesn't allow it.
>>> I would assign the responsibility differently: because the programmer
>>> uses a null expression to initialize a variable, a
>>> NullPointerException is generated when the variable is used.  If the
>>> null-initialized variable is used inside the statement, there is an
>>> additional suppressed exception because of the guarantee to implicitly
>>> call close on the value when the block is exited.
>> The main problem is what if a try-with-resources have a body that don't
>> use the variable.
>> Let suppose I design this interface:
>>
>> interface Transaction extends AutoCloseable {
>>      public void close() throw TransactionException;
>> }
>>
>> I want to use it with a try-with-resources to use its marvellous
>> suppressed exceptions management:
>>
>> try(Transaction t = Transactions.getTransaction()) {
>>      // I don't use 't' here
>> }
>>
>> here if the transaction is null, it will execute the body and then
>> throws a NPE as a sidekick.
>> I don't think it's the behaviour we want.
>>
>>>> If you take a look to how try/finally are coded now, you will find a
>>>> nullcheck
>>>> before the try or in the finally block.
>>>>
>>>>>> I don't like the idea that a user mistake blow up in a generated code,
>>>>>> try-with-resources should protect itself.
>>>>> I think this stack trace can easily be explained to programmers by
>>>>> saying "the suppressed exception comes from the close call you did
>>>>> have to write yourself."
>>>> You don't have to explain something which is not surprising.
>>> New features should be expected to require nonzero explanation simply
>>> by virtue of being new.
>> I have no problem to teach why try-with-resources was introduced or when
>> to use it.
>> I just don't want to explain why try-with-resources doesn't behave like
>> all other Java constructs.
>> They all works in a similar way:
>>      synchronized(null) { }, for(Object o: null) { }, switch(null) { }
>> why try(Object o = null) {} should have a different semantics ?
> It depends on your idea of "similar way'. The 'similar way' that
> correctly describes all these behaviours (including try) is "as soon as
> (but no sooner than) the value is dereferenced to access a field or call
> a method"
>
> To answer relative to your idea of 'similar way' which focuses on the
> syntactic parts of the statements, it is because all those examples
> require the null to be dereferenced prior to executing the block.
>
> 'synchronized' because you need to synchronize on an object (not null)
> before executing the block
> 'for' because you need to get an Iterator from the Iterable and call
> next() etc before executing the block
> 'switch' because you need to evaluate the null to determine which case
> block to execute (although if the language allowed null as a case then
> this requirement would disappear)
>
> 'try' however does not need to do anything with the null before
> executing the block. Basically it is just saying remember this till
> after the block then do something with it (close it).
>
> It is the semantics of those statements that requires the NPE for null
> before the block. Try doesn't have those semantics, switch could be
> designed either way, but was designed to require it, if it was designed
> to allow null case for String and enum, then there would be no NPE till
> maybe in the null case or default case blocks, making it like try.
>
> try behaves differently because the other constructs cannot behave like try.
>
> Bruce

All cited construct including try() may dereference a value hence throw 
a NPE.

switch(null) can jump to default or at the end of the switch block,
if there is no default.
for(Object o:null) { } can do not execute it's body.
synchronized(null) can synchronized to a special token.
try(Object o = null) { } can do not execute it's body

But for switch, for and synchronized it was chosen to throw a NPE.

Rémi






More information about the coin-dev mailing list