-
Bug
-
Resolution: Fixed
-
P5
-
1.3.1
-
1.4
-
generic
-
generic
-
Not verified
Name: bsC130419 Date: 07/27/2001
java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build Blackdown-1.3.0-FCS)
Java HotSpot(TM) Client VM (build Blackdown-1.3.0-FCS, mixed mode)
but also:
java version "1.1.6"
Hello,
I encountered some inconsistency in the Java Language Specification, and want to
discuss some solutions, if the problem is not already well known.
During my ongoing effort to formalize the Java package/access concept in the
theorem prover Isabelle, I noticed some discrepancy between the
Java Language Specification (Second Edition) and the implementation of Java in
the JDK (version 1.3.0). Strictly speaking the implementation is wrong with
respect to the specification.
The problem arises in the combination of inheritance/overriding and access
control.
First I will cite the relevant parts of the Java Language Specification and
then give a short example to illustrate the discrepancy. Finally I
propose an addition to the Java Language Specification which will hopefully
solve the problem.
----------- Java Language Specification---------------------------------------
8.4.6.1 Overriding (by Instance Methods)
An instance method m1 declared in a class C overrides another method
with the same signature, m2, declared in class A if both
1. C is a subclass of A.
2. Either
a) m2 is non-private and accessible from C, or
b) m1 overrides a method m3, m3 distinct from m1, m3 distinct from m2,
such that m3 overrides m2.
------------------------------------------------------------------------------
So to decide whether one method overrides another we need to know more about
accessibility:
----------- Java Language Specification---------------------------------------
6.6.1 Determining Accessibility
A package is always accessible. If a class or interface type is declared
public, then it may be accessed by any code, provided that the compilation
unit (?7.3) in which it is declared is observable. If a top level class or
interface type is not declared public, then it may be accessed only from
within the package in which it is declared. An array type is accessible
if and only if its element type is accessible.
A member (class, interface, field, or method) of a reference
(class, interface, or array) type or a constructor of a class type is
accessible only if the type is accessible and the member or constructor
is declared to permit access:
If the member or constructor is declared public, then access is
permitted.
All members of interfaces are implicitly public.
Otherwise, if the member or constructor is declared protected, then
access
is permitted only when one of the following is true:
Access to the member or constructor occurs from within the package
containing the class in which the protected member or constructor is
declared.
Access is correct as described in ?6.6.2.
Otherwise, if the member or constructor is declared private, then access
is permitted if and only if it occurs within the body of the top level
class (?7.6) that encloses the declaration of the member.
Otherwise, we say there is default access, which is permitted only when
the access occurs from within the package in which the type is declared.
------------------------------------------------------------------------------
So for a member to be accessible it is necessary that the class of the member
is accessible.
Consider the following Java Program:
package APackage;
class A
{
public void foo(){System.out.println("A.foo called");}
}
package APackage;
public class AS extends A
{
public void call_fooA(A a) {
System.out.println("AS.call_fooA called; calling foo");
a.foo();
}
public void call_fooAS(AS as) {
System.out.println("AS.call_fooAS called; calling foo");
as.foo();
}
}
package BPackage;
import APackage.AS;
public class B extends AS
{
public void foo(){System.out.println("B.foo called");}
}
package Main;
import APackage.AS;
import BPackage.B;
public class InvisibleInheritance
{
public static void main(String args[])
{
B b = new B();
AS as = new AS();
System.out.println("calling as.call_fooAS(b)");
as.call_fooAS(b);
System.out.println("calling as.call_fooA(b)");
as.call_fooA(b);
}
}
Class A is not declared public, so it is accessible only from within its
package (APackage). A declares a public method foo().
Class AS extends A and is declared public. So it can also be accessed from
outside of the package APackage. AS inherits the method foo() from A.
Class B is defined in a different package than A and AS. So it can access AS
but
not A. Hence it can access AS.foo() but not A.foo(). B declares a new
method foo() with the same signature as A.foo().
What about overriding?
First of all I want to clarify the notion of "declare" I will use. The exact
meaning of "declare" isn't defined explicitely in the Java Language
Specification. But it implicitely gets clear if we look at the following phrase
of the specification:
----------- Java Language Specification---------------------------------------
8.2 Class Members
The members of a class type are all of the following:
Members inherited from its direct superclass (?8.1.3), except in class Object,
which has no direct superclass
Members inherited from any direct superinterfaces (?8.1.4)
Members declared in the body of the class (?8.1.5)
------------------------------------------------------------------------------
So if a Class X "declares" a method m this means to me, that m is
declared in the body of the class itsself, it is not an inherited method!
Strictly referring to the language specification we have
the following situation:
B.foo() doesn't override A.foo(), since
1. B is a subclass of A
but neither 2 a) nor 2 b) holds.
2 a) A.foo() is not accessible from B, since A is not accessible from B
Remark: The Rule 2 a) doesn't give us the freedom to say that
A.foo() is accessible, because AS inherits it, and AS.foo()
is accessible from B! There is no word about subclasses or
inheritance in this rule.
2 b) there is no intermediate method that overrides A.foo() which is
overridden by B.foo(). (AS.foo() is just inherited from A.foo(),
it does not override A.foo()).
B.foo() doesn't override AS.foo(), since it is senseless to talk of the
term "override" in this case, since no method foo() is declared in AS (it
is just inherited from A)
Therefor the calls to as.callFooAS(b) and as.callFooA(b) should lead to the
same result. They should both call A.foo():
callFooAS(b):
Inside of this method the expected static type of the parameter is AS,
the dynamic type is B. So calling foo should invoke A.foo(), since B.foo()
doesn't override A.foo()
callFooA(b):
Inside of this method the expected static type of the parameter is A,
the dynamic type is B. So calling foo should invoke A.foo(), since B.foo()
doesn't override A.foo().
The Java compiler (for Java version 1.3.0) has a different opinion about this.
Executing the program gives us the following output:
calling as.call_fooAS(b)
AS.call_fooAS called; calling foo
B.foo called
calling as.call_fooA(b)
AS.call_fooA called; calling foo
B.foo called
In both cases B.foo is called, so the current implementation considers
B.foo() to override A.foo().
The concepts of classes/inheritance and packages/access doesn't seem to fit
together very well in this special situation. The origin of the problem is, that
the method A.foo() is defined with public access but the class A itself is
only defined with package access. The method becomes visible due to inheritance
in the class AS. The problem would not occur if the method A.foo() were
defined with default (package) access. Then it has to be overridden in the
class AS, by a method foo() with public access, to be visible from
outside the package. Let's look at these modifications to the method
declarations in more detail:
A.foo() (* default package access *)
public AS.foo()
public B.foo()
AS.foo() overrides A.foo:
1.AS is a subclass of A
2 a) A.foo() is non-private and accessible from AS, since AS and A are
inside the same package.
But now B.foo() also overrides A.foo(), because:
1.B is a subclass of A
2 b) there is the intermediate method AS.foo().
B.foo() overrides AS.foo(), AS.foo() distinct from B.foo(),
AS.foo() distinct from A.foo and AS.foo() overrides A.foo().
I think this is the reason for the rule 2 b) of overriding in the Java
Language Specification.
The problem seems to be known to the language designers, since
in JavaCard this problematic situation is avoided by a constraint which
enforces the just described modifications.
Concerning the JavaCard 2.1.1 Virtual Machine Specification page 9,
"a package-visible class that is extended by a public class cannot define
any public or protected methods".
In principle there are three solutions to remove the inconsistency.
1. Implement Java according to its specification.
2. Restrict the access control as in case of JavaCard.
3. Adjust the specification to fit the implementation.
Solutions 1 and 2 seem to be utopian, since Java is widely spread and used with
the "de facto" standard semantics of the implementation.
To be realistic I think the Java Language Specification should be adjusted to
the "de facto" standard, defined in the JDK. (Thats real life: The
specification
must be adjusted to fit the implementation!). So I want to discuss some
proposal to fix the definition of overriding:
An instance method m1 declared in a class C overrides another method
with the same signature, m2, declared in class A if both
1. C is a subclass of A.
2. Either
a) m2 is non-private and accessible from C, or
b) m1 overrides a method m3, m3 distinct from m1, m3 distinct from m2,
such that m3 overrides m2, or
c) There is an intermediate class B, C is a subclass of B and B is a
subclass of A, so that m1 is inherited by B and m1 gets accessible
from C through to this inheritance (B.m1 is accessible
from C).
Norbert Schirmer
(Review ID: 128958)
======================================================================
- relates to
-
JDK-4493343 wrong implementation of "inheritance" and "accessibility of methods"
- Closed
-
JDK-4642788 8.4.6.1 Overriding is unclear.
- Closed