Woebegone said:
According to 6.6.2.1 in the JLS, it looks as though a superclass can
directly refer to a subclass method through a reference to an object of
subclass type, as long as the method is accessible in the superclass.
I'm not sure if we are misunderstanding each other here, or just disagreeing on
a question of fact. Anyway on the assumption that there is still some point to
this discussion...
This is the exact case I am considering:
====== A.java =======
package a;
public class A
{
protected void method() { System.out.println("in A.method()"); }
public static void main(String[] args)
{
b.B theB = new b.B();
A aliasForTheB = theB;
aliasForTheB.method(); // allowed, calls b.B.method()
theB.method(); // illegal: access not permitted
}
}
====== B.java =======
package b;
public class B extends a.A
{
protected void method() { System.out.println("in B.method()"); }
}
=============
This example fails to compile using Sun's JDK 1.4.2-b28, although it does
compile under JDK 1.3.
I believe that the 1.4 behaviour is correct since the relevant section states:
Let C be the class in which a protected member m is declared.
Access is permitted only within the body of a subclass S of C.
Now b.B.method() is declared in b.B, but the disallowed call is being made from
a.A, which is *not* a subclass of b.B (or in the same package), hence it is
illegal.
The spec then goes on to say:
In addition, if Id denotes an instance field or instance method, then:
• If the access is by a qualified name Q.Id, where Q is an ExpressionName,
then the access is permitted if and only if the type of the expression Q is
S or a
subclass of S.
• If the access is by a field access expression E.Id, where E is a Primary
expression, or by a method invocation expression E.Id(. . .), where E is a
Primary expression, then the access is permitted if and only if the type of
E is
S or a subclass of S.
which I suspect is what you are focussing on. However -- IMO -- these extra
conditions are irrelevant since the initial condition has failed.
It is important to realise (as the 1.3 compiler writers apparently did not ;-)
that the declaration of a.A.method() is totally irrelevant here. The compiler
is required to consider the call to b.B.method() exactly as if it were not
overriding anything. The reason for this is in the (very important, but often
overlooked) Binary Compatibility section of the spec. Code of the following
form (untested):
====== A.java =======
package a;
public class A
{
public void method() { System.out.println("in A.method()"); }
}
====== B.java =======
package b;
public class B extends a.A
{
public void method() { System.out.println("in B.method()"); }
}
====== C.java =======
package c;
public class C
{
public static void main(String[] args)
{
b.B theB = new b.B();
theB.method();
}
}
=============
this code must continue to work if classes c.C and b.B are loaded into the JVM
with a newer version of class a.A that does not include method().
That is, the compiler *must not* say "ah, the call to theB.method() is an
override of the a.A.method, so I will generate code with an embedded reference
to a.A.method() that will be resolved to b.B.method() by the runtime
machinery".
The 1.3 compiler gets this wrong and does not ignore a.A.method() as it should.
I managed to find a 1.3 compiler and this is the disassembled output of
a.A.main() from the first example in this post (this is disassembled with
gnoloo, since javap can't be persuaded to show enough detail):
=============
..method public static main ([Ljava/lang/String
V
new b/B
dup
invokespecial b/B/<init> ()V
astore_1
aload_1
astore_2
aload_2
invokevirtual a/A/method ()V
aload_1
invokevirtual a/A/method ()V
return
=============
The second last line is wrong -- javac is generating a call to a.A.method()
when it should be generating a call to b.B.method().
Access control is a static concept, and it applies to the declared
types of the variables involved, not to the actual types of the objects
those variables refer to at runtime[*].
Agreed, but what I'm saying is that in the case where a superclass might
implicitly gain access dynamically to a given implementation, the compiler
will statically allow direct reference.
No it doesn't, and no it shouldn't ;-)
1.3 is broken in this respect, but that is Just Another Bug(tm).
-- chris