This question is derived from another topic called "strange notation".
I started a new topic because I am interested in a subtle aspect of the original question. The code has been changed slightly to emphasize my point.
The output is 6.
This leads to an important observation which is (perhaps) not so obvious.
Array index expressions - and more generally method parameter expressions - are computed before testing to see whether the object reference is valid.
From a performance perspective, it might be more sensible if the JVM would test for a valid reference (which is probably one machine instruction) before it computes all the parameters (which could be tens of thousands of machine instructions if the parameter expressions involve method calls, etc.).
Is this behavior defined by the Java language? Or is it JVM specific?
It's specified in JLS 15.13.1. Is this unnecessarily slow from a performance perspective? In my opinion, no. It's only an issue if the programmer has screwed up and provided a null reference; if that happens, the performance issue is extremely minor compared to the fact that you're throwing a NullPointerException. If the programmer has not screwed up, then I think it makes no difference performancewise when the check is performed. It's probably simplest to perform the check immediately before the array is actually used, since that's also the point at which an ArrayIndexOutOfBoundsException would be thrown if the array index is invalid. You can't possibly perform the latter check without knowing both the array reference and the index. It makes a certain sense to me to put those two checks together at nearly the same time, even though the null check could have been performed earlier.
OK, so I wanted to write a little bit of code that would fail if the check was done first, and work if it was done second. When I compile it with either JDK 1.4.2 or 1.5, it gets an NPE, which surprised me. Here's the code:
18:aload_1 // Push array1 (null) on the stack 19:aload_2 // Push array2 on the stack 20:dup // Push array2 again 21:astore_1 // Store array2 into array1, leaving array2 on the stack 22:iconst_1 23:iaload // Read an item from array2, leaving array1/null on the stack 24:iconst_4 25:iastore // Try to store 4 into null, and NPE
Note what's happening: we effectively check for the null reference after the index is computed, but the reference we check is the one from before the index was computed. Jim, want to explain if this one's intentional?
I think it's intentional. It's an extension of the "evaluate left before right" theme that runs thoroughout the rules of evaluation. In an array access expression list foo[bar], the array reference foo is left of the index bar, so it's evaluated first. When later you evaluate the right expression bar, there may be side effects changing foo, but we ignore them here because foo has already been evaluated. If we were able to check the value of foo after the expression was evaluated, we'd find that yes, it's been changed as a side effect of evaluating bar. But that new value does not affect the evaluation of the array access expression - it's only visible after that evaluation.
In a sense, it's not unlike this situation:
The final values are x = 1, y = 1. The y = 1 comes about because when (x + (++x)) is evaluated, the x on the left evaluates to 0 and the one on the right evaluates to 1. The fact that x is now 1 does not change the value that the first x had at the time it was evaluated. [ July 14, 2006: Message edited by: Jim Yingst ]
Joined: May 09, 2006
Indexing arrays and passing parameters to methods both behave the same. If you think about method calls, maybe there is some logic in this behavior.
This is from Chapter 15 on Expressions:
15.12.4 Runtime Evaluation of Method Invocation
At run time, method invocation requires five steps. First, a target reference may be computed. Second, the argument expressions are evaluated. Third, the accessibility of the method to be invoked is checked. Fourth, the actual code for the method to be executed is located. Fifth, a new activation frame is created, synchronization is performed if necessary, and control is transferred to the method code.
Testing for a null pointer is an aspect of Step 3.
Evaluating the parameter expressions is part 2.
It makes sense that the behavior should be consistent. Step 3 "determining whether the method to be invoked is accessible", in our case, means checking to see if the pointer is null. But it could also mean, in a more complicated case, that a whole bunch of parameter marshaling needs to be done when we are doing RMI. Right? Maybe this is the reason for the ordering.
I am just guessing.
But for a remote method invocation, we need to evaluate parameters before we can see whether our (remote) object reference will find the method it is looking for.
What do you think? [ July 14, 2006: Message edited by: Douglas Chorpita ]