JDK-8300691 - final variables in for loop headers should accept updates
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Wed Oct 23 10:14:19 UTC 2024
On 22/10/2024 19:35, Archie Cobbs wrote:
> This made me realize this issue really is all just about naming. For
> example, outer instances are captured just like variables, but we
> don't have a problem there because the captured version of the
> variable has a different name inside the nested class vs. outside
> (i.e., "Outer.this" vs. "outer", if you create the nested class via
> "outer.new Inner()").
I think this observation hits the nails on the head.
When we capture “fields” we don’t really capture mutable boxes, we
capture an (immutable) reference to the enclosing class.
I say immutable because the enclosing class reference is snapshotted, at
construction - e.g. if you have:
|Outer outer = new Outer(); // 1 Outer.Inner inner = outer.new Inner();
outer = new Outer(); // 2 |
This is all legal code. One might wonder: does this code mean that
Inner’s enclosing instance is updated in (2)? No, because, really, it’s
as if the value of “outer” was snapshotted when we created Inner, and a
reference to that snapshotted value was saved. Since we access that
snapshotted reference using |Outer.this| (and not |outer|), we sort of
preserve the illusion that snapshotting doesn’t occur here - the class
is accessing its “enclosing instance” which is a separate thing from a
“field defined in the enclosing class”.
So yes, a big part of this exercise is in trying to keep variable names
meaning the same thing no matter where they appear (captured or not). If
we start snapshotting everything, then you have two version of a local,
the one snapshotted and captured e.g. inside a lambda, and the one
(maybe mutable!) available outside.
My subjective opinion here is that, if this principle is important (and,
as John mentioned, it was very painfully front and center when inner
classes were first added to the language), giving this principle up to
allow capture in an extra 20-30 imperative loops (in an entire codebase)
doesn’t look a great deal. That is, reducing asymmeties between counted
loops and for-each loop comes at a price: now capture variables no
longer mean the same thing they do in their non-captured context.
Of course you can argue it both ways: by making the new capture
treatment only available for induction variables in for loops (where the
variable is not mutated in the body) we do reduce (not eliminate!) the
chances of somebody observing different values for the same induction
variable. Is that enough to put our minds at ease? Overall, it seems to
me that this a very subjective topic: there is no well-defined principle
as to why imperative for loops should behave any different than they do
today, as there is (likely) no well-defined principle as to why for-each
loop behaves the way it does today.
How you "fix" this largely depends on (a) how you find this asymmetry
annoying (which likely depends on exposure - and different code bases
might have different levels of that) and (b) how much your developer
intuition is trained to view the loop induction variable as a single
mutable *variable*, or a series of *values* where each value is derived
from the former in a controlled fashion. I don't have a strong (enough)
opinion on either :-)
Maurizio
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20241023/714141b2/attachment-0001.htm>
More information about the amber-dev
mailing list