This week's book giveaway is in the Mac OS forum. We're giving away four copies of a choice of "Take Control of Upgrading to Yosemite" or "Take Control of Automating Your Mac" and have Joe Kissell on-line! See this thread for details.
When switching to Java 6 from Java 1.4, a piece of code stopped compiling. The piece of code looks alright to me, and it ran fine under Java 1.4, suggesting a compiler bug. However, possibly I have missed some subtle reason why the code is illegal.
To make it easy for Ranchers, I have written a tiny piece of code that exhibits the same behaviour as the complicated real code.
This doesn't compile. It says: -
C:\users\peterc>%CV_JDK_1_6_0%\bin\javac TestNested.java %CV_JDK_1_6_0%\bin\javac TestNested.java TestNested.java:28: non-static variable this cannot be referenced from a static context new Inner().baz(); ^ 1 error
Originally posted by Jesper Young: Well, class Inner is private in class TestNested, so it isn't strange that you can't access it in class Nested which extends Nested - private members are not inherited.
... except that I thought "private" didn't have any effect between inner and outer classes; they can all see each others privates anyway (!).
I agree with you, I don't believe the private declaration is an issue here. If you removed the abstract modifier from TestNested and the static modifier from nested this would work with the existing access modifiers.
I know that it's extremely frustrating (actual sentiment censored for this forum) when behavior changes between compiler releases, but in IMO the way 1.6 treats this code seems consistent with the way other instance members are treated when you attempt to access from a static context. We've all seen the "method cannot be accessed from static context" errors when we first starting learning java and tried to access an instance method from within main without having an instance. This seems to be the exact same thing, only with an instance class instead of an instance method. It's just unfortunate that this has been allowed for so long, and now changing it is obviously breaking people's code.
It looks to me like the private declaration is an issue here. Both at the practical level (removing "private" allows the code to compile) and at the specification level. But that's far from obvious. Part of the issue is that there's a subtle difference between being inherited and being accessible. The definition of inherited refers to "accessible members of a superclass or superinterface which are neither private nor hidden nor overridden". The class Inner is acccessible from within class Nested, but it's still a private class - and thus Inner is not inherited by Nested, and Inner is not a member of Nested.
So why does this matter? Well, going further into the murk of the API, we have JLS 15.9.2. Read through this carefully to see how it applies to "new Inner()" inside the class Nested.
"Let C be the class being instantiated, and let i the instance being created."
OK, C refers to Inner, and i is the particular instance we're trying to instantiate.
"If C is an inner class..."
"...then i may have an immediately enclosing instance. The immediately enclosing instance of i (�8.1.3) is determined as follows:
"If C is an anonymous class..."
It isn't, so skip this.
"If C is a local class..."
Again, skip this section.
"Otherwise, C is an inner member class (�8.5)."
"If the class instance creation expression is an unqualified class instance creation expression"
"If the class instance creation expression occurs in a static context, then a compile-time error occurs."
We're inside the non-static method foo(), so no.
"Otherwise, if C is a member of an enclosing class"
"then let O be the innermost lexically enclosing class of which C is a member"
Here's where it gets fun. From where we are inside the method foo(), the innermost lexically enclosing class is the anonymous Runnable class. But Inner is not a member of this class. The next lexically enclosing class is Nested. But because Inner is private, Inner is not a member of Nested. It's accessible (being inside the same top-level class), but not a member. So now we come to the next lexically enclosing class, which is TestNested. Inner is a member of TestNested. So, O refers to TestNested. (Whew!)
"and let n be an integer such that O is the nth lexically enclosing class of the class in which the class instance creation expression appears."
OK, TestNested was the 3rd lexically enclosing class around "new Inner()", so n = 3.
"The immediately enclosing instance of i is the nth lexically enclosing instance of this."
So, the immediately enclosing instance of our new Inner() instance is the 3rd lexically enclosing instance of this. What does that mean? Well, from where we are, "this" would refer to the instance of the anonymous Runnable class. That's the 1st enclosing instance of this. Then there's Nested.this, that's the 2nd enclosing instance of this. So the 3rd enclosing instance of this, according to the JLS, must be TestNested.this. Except - that doesn't exist. Which is exactly what the compiler is complaining about here.
Now what seems to be missing here is a clear statement that it's a compile-time error if the nth enclosing instance doesn't exist. I don't see one. And this section did start by saying there may be an enclosing instance. Other inner class constructors don't necessarily have them. But we followed the rules carefully and got to a statement saying that there was an enclosing instance - except that instance doesn't actually exist. So I guess we are supposed to infer that because the code led to a logical contradiction, that's a compiler error. (As opposed to other JLS contradictions where we just try to make the best of things.) So: I'm not saying this is unambiguous, but I do think this is the logic the compiler is now following (and probably should have followed all along).
In contrast, if we remove the "private" from the declaration of Inner, then Inner is a member of Nested as well as TestNested. And so O, the innermost lexically enclosing class of which C is a member, now refers to Nested rather than TestNested. And i = 2, and the immediately enclosing instance of i is the 2nd lexically enclosing this instance, which is Nested.this, which does, indeed, exist. So the code compiles.
Now, aren't you glad you asked?
I need a beer. [ April 20, 2007: Message edited by: Jim Yingst ]
"I'm not back." - Bill Harding, Twister
subject: Why doesn't this inner class compile under Java 6?