JavaC and Jikes both violate JLS calling super of inner class

M

Mike Schilling

Take the following code:

public class Outer {
String className;

public static void main(String[] args) {
Outer amb = new Outer();
SubOuter sa = amb.new SubOuter();
SubOuter.SubInner subIn = sa.new SubInner();
System.out.println(subIn.getSuperEncloserName());
}

Outer() {
className = "Outer";
}

class Inner {
String getEncloserName() {
return className;
}
}

class SubOuter extends Outer {
SubOuter() {
className = "SubOuter";
}

class SubInner extends Inner {

SubInner() {
super(); // ******
}

String getSuperEncloserName() {
return super.getEncloserName();
}
}
}
}

When compiled with either javac or jikes and then run, the output is
"SubOuter".

Let's analyze what happens at the starred line. We must determine the
immediately enclosing instance of "this" with respect to class Inner.
"this", an instance of SubInner, has an immediately enclosing instance
SubOuter.this, which in turn has an immediately enclosing instance
Outer.this. According to JLS 8.8.5.1:

Let O be the innermost lexically enclosing class of which S is a member,
and let n be an integer such that O is the nth lexically enclosing class
of C.
The immediately enclosing instance of i with respect to S is the nth
lexically
enclosing instance of this.

Here, S is Inner, O Outer, and C SubInner. By this rule, the enclosing
instance should be Outer.this (the 2nd lexically enclosing instance of
"this"), since the type of the selected enclosing instance of "this" must
exactly match the innermost lexically enclosing class of S. As running the
code shows, the enclosing instance is actually SubOuter.this (the first
lexically enclosing instance of "this".) This is what I would have naively
assumed: that the enclosing instance of "this" needs to be assignment
compatible with the enclosing class of S, not that it needs to be identical.
In other words, one would expect the following to compile correctly:

public class Outer2 {
class Inner {
}
}

public class SubOuter2 extends Outer2 {
class SubInner extends Outer2.Inner {
SubInner() {
super();
}
}
}

In fact it does. Qualifying the super call as "Outer2.this.super()" is not
required.

Thus, 8.8.5.1 should read something like:

Let O be the innermost lexically enclosing class of which S is a member,
and let n be the smallest integer such that O or a subclass of O is the
nth
lexically enclosing class of C
The immediately enclosing instance of i with respect to S is the nth
lexically
enclosing instance of this.

This matches both our expectations and what seems to be implemented by javac
and jikes. Note that a similar correction is needed in 15.9.2, which
describes the behavior of the constructor of an anonymous class whose
superclass in an inner class.
 
S

Steven Coco

Mike said:
Take the following code:

I'm sorry: I didn't completely read your synopsis of the Lang spec.
What is it that seems wrong to you--the output is correct.

--

..Steven Coco.
.........................................................................
When you're not sure:
"Confess your heart" says the Lord, "and you'll be freed."
 
M

Mike Schilling

Steven Coco said:
I'm sorry: I didn't completely read your synopsis of the Lang spec.
What is it that seems wrong to you--the output is correct.

The spec says that the type for enclosing instance for the superclass must
exactly match the type of an enclosing instance of the subclass. That isn't
what's happening.
 
S

Steven Coco

OK. I see where you're at. You said:
As running the code shows, the enclosing instance is actually
SubOuter.this

It boils down to this:

In this bit of code:
SubOuter() {
className = "SubOuter";
}

*I* ... naively assumed that the variable className would refer to the
member of the inner class's enclosing instance--because it is lexically
defined that way [just look up the page 21 lines] ~ sorry.

But I am IN LUCK! BECAUSE;

section 8.1.5 of the spec says (and I quote! ;) ):

The scope of a declaration of a member m
declared in or inherited by a class type C
is the entire body of C, including any nested
type declarations.

I _do_ hope we could still be friends! Please see below.

Thank you, and God bless you.

But just in case I would leave you on a hook, I think what you want to
do is use "this.className = "SubOuter";".
 
D

Dobromir Gaydarov

Well, the confusion here is what exactly super.getEncloserName() does.
It does call the inheritted (as oppoused to the overriden method).
But it does not change 'this' in any way.

To make it clear do the following:
1. Override getEncloserName() to return some other fixed value
2. Execute the code again

Now you see what is going on - you call the Inner.getEncloserName instead of
the SubInner.getEncloserName.
But what is 'this' during this call? Normally, it is a SubInner object, so
the value of className there is "SubOuter".

Regards,
Dobromir

Mike Schilling said:
Take the following code:

public class Outer {
String className;

public static void main(String[] args) {
Outer amb = new Outer();
SubOuter sa = amb.new SubOuter();
SubOuter.SubInner subIn = sa.new SubInner();
System.out.println(subIn.getSuperEncloserName());
}

Outer() {
className = "Outer";
}

class Inner {
String getEncloserName() {
return className;
}
}

class SubOuter extends Outer {
SubOuter() {
className = "SubOuter";
}

class SubInner extends Inner {

SubInner() {
super(); // ******
}

String getSuperEncloserName() {
return super.getEncloserName();
}
}
}
}

When compiled with either javac or jikes and then run, the output is
"SubOuter".

Let's analyze what happens at the starred line. We must determine the
immediately enclosing instance of "this" with respect to class Inner.
"this", an instance of SubInner, has an immediately enclosing instance
SubOuter.this, which in turn has an immediately enclosing instance
Outer.this. According to JLS 8.8.5.1:

Let O be the innermost lexically enclosing class of which S is a member,
and let n be an integer such that O is the nth lexically enclosing class
of C.
The immediately enclosing instance of i with respect to S is the nth
lexically
enclosing instance of this.

Here, S is Inner, O Outer, and C SubInner. By this rule, the enclosing
instance should be Outer.this (the 2nd lexically enclosing instance of
"this"), since the type of the selected enclosing instance of "this" must
exactly match the innermost lexically enclosing class of S. As running the
code shows, the enclosing instance is actually SubOuter.this (the first
lexically enclosing instance of "this".) This is what I would have naively
assumed: that the enclosing instance of "this" needs to be assignment
compatible with the enclosing class of S, not that it needs to be identical.
In other words, one would expect the following to compile correctly:

public class Outer2 {
class Inner {
}
}

public class SubOuter2 extends Outer2 {
class SubInner extends Outer2.Inner {
SubInner() {
super();
}
}
}

In fact it does. Qualifying the super call as "Outer2.this.super()" is not
required.

Thus, 8.8.5.1 should read something like:

Let O be the innermost lexically enclosing class of which S is a member,
and let n be the smallest integer such that O or a subclass of O is the
nth
lexically enclosing class of C
The immediately enclosing instance of i with respect to S is the nth
lexically
enclosing instance of this.

This matches both our expectations and what seems to be implemented by javac
and jikes. Note that a similar correction is needed in 15.9.2, which
describes the behavior of the constructor of an anonymous class whose
superclass in an inner class.
 
M

Mike Schilling

Steven Coco said:
OK. I see where you're at. You said:
As running the code shows, the enclosing instance is actually
SubOuter.this

It boils down to this:

In this bit of code:
SubOuter() {
className = "SubOuter";
}

*I* ... naively assumed that the variable className would refer to the
member of the inner class's enclosing instance--because it is lexically
defined that way [just look up the page 21 lines] ~ sorry.

But I am IN LUCK! BECAUSE;

section 8.1.5 of the spec says (and I quote! ;) ):

The scope of a declaration of a member m
declared in or inherited by a class type C
is the entire body of C, including any nested
type declarations.

I _do_ hope we could still be friends! Please see below.

Thank you, and God bless you.

But just in case I would leave you on a hook, I think what you want to
do is use "this.className = "SubOuter";".

Try it; the behavior won't change. If a field name could either refer to a
field inherited from a superclass or a field from an enclosing instance, the
superclass wins.
 
M

Mike Schilling

Dobromir Gaydarov said:
Well, the confusion here is what exactly super.getEncloserName() does.
It does call the inheritted (as oppoused to the overriden method).
But it does not change 'this' in any way.

Precisely. If a class has one or more inner classes in its inheritnace
chain, there is an innermost enclosing instance defined with respect to each
ancestor class which is an inner class. The method "getEncloserName" is
intended to find out what the enclosing instance with respect to the class
"Inner" is.
To make it clear do the following:
1. Override getEncloserName() to return some other fixed value
2. Execute the code again

Now you see what is going on - you call the Inner.getEncloserName instead of
the SubInner.getEncloserName.
But what is 'this' during this call? Normally, it is a SubInner object, so
the value of className there is "SubOuter".

But that's not how it works. Field names are bound at compile time, not run
time. "className" means "the field className in the type of 'Inner', as
determined by the compiler." In this case it resolves to
this.Outer.className. The fact that the example prints "SubOuter" proves
that this.Outer.className is a SubOuter, in violation of the JLS.
 
M

Mike Schilling

Steven Coco said:
OK. I see where you're at. You said:
As running the code shows, the enclosing instance is actually
SubOuter.this

It boils down to this:

In this bit of code:
SubOuter() {
className = "SubOuter";
}

*I* ... naively assumed that the variable className would refer to the
member of the inner class's enclosing instance--because it is lexically
defined that way [just look up the page 21 lines] ~ sorry.

No, it refers to this.className. Names inherited from ancestors hide names
inherited from enclosing instances.
But I am IN LUCK! BECAUSE;

section 8.1.5 of the spec says (and I quote! ;) ):

The scope of a declaration of a member m
declared in or inherited by a class type C
is the entire body of C, including any nested
type declarations.
Yes, the name is still in scope, but hidden by the scope of the superclass.
I _do_ hope we could still be friends! Please see below.

Thank you, and God bless you.

But just in case I would leave you on a hook, I think what you want to
do is use "this.className = "SubOuter";".

Try it. Same result. In fact, same generated bytecode.
 
N

Neal Gafter

Mike said:
Let's analyze what happens at the starred line. We must determine the
immediately enclosing instance of "this" with respect to class Inner.
"this", an instance of SubInner, has an immediately enclosing instance
SubOuter.this, which in turn has an immediately enclosing instance
Outer.this. According to JLS 8.8.5.1:

Let O be the innermost lexically enclosing class of which S is a member,
and let n be an integer such that O is the nth lexically enclosing class
of C.
The immediately enclosing instance of i with respect to S is the nth
lexically
enclosing instance of this.

Here, S is Inner, O Outer, and C SubInner. By this rule, the enclosing
instance should be Outer.this (the 2nd lexically enclosing instance of
"this"), since the type of the selected enclosing instance of "this" must
exactly match the innermost lexically enclosing class of S.

No, it need not exactly match. It is the innermost lexically exclosing class of
which S is a member. SubOuter is indeed a lexically enclosing class of which
SubInner is a member. In fact, it is an inherited member.
 
S

Steven Coco

Mike said:
Try it. Same result. In fact, same generated bytecode.

Oops! That's still right since there is no instance of Outer other than
the one you instantiated.

--

..Steven Coco.
.........................................................................
When you're not sure:
"Confess your heart" says the Lord, "and you'll be freed."
 
M

Mike Schilling

Neal Gafter said:
No, it need not exactly match. It is the innermost lexically exclosing class of
which S is a member. SubOuter is indeed a lexically enclosing class of which
SubInner is a member. In fact, it is an inherited member.

S is Inner, not SubInner, so I presume you meant to say "SubOuter is indeed
a lexically enclosing class of which Inner is a member. In fact, it is an
inherited member." And this is inded the answer. As 8.2 makes clear, the
members of a class type include members inherited from the direct
superclass. I was missing the fact that this applies to member classes as
well as fields and methods. (The fact that java.util.Class defines both
getClasses() and getDeclaredClasses() methods should have been a clue.)

Thanks,
Mike
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,982
Messages
2,570,189
Members
46,734
Latest member
manin

Latest Threads

Top