David is right, the code should give 20/20. But I suppose it's possible that you're using a compiler which gets this wrong. For the sake of argument, then, tell us what you're using to compile and run this?
OK, I tried running it, and I get 10/20 too, no warnings (1.6.0_15 on Mac OS X).
Hmmm. I suppose the Eclipse warning is the key to what's going on. Because the Top foo() method is hidden from Bottom, the like-named method does not override it; Top.foo() and Bottom.foo() are entirely unrelated methods. If Top.foo() were private, exactly the same thing would happen, for exactly the same reason (you'd have to move main() into Top, of course) but then it wouldn't be so surprising.
An instance method m1 declared in a class C overrides another instance method, m2, declared in class A iff all of the following are true:
C is a subclass of A.
The signature of m1 is a subsignature (§8.4.2) of the signature of m2.
m2 is public, protected or declared with default access in the same package as C, or
m1 overrides a method m3, m3 distinct from m1, m3 distinct from m2, such that m3 overrides m2.
Moreover, if m1 is not abstract, then m1 is said to implement any and all declarations of abstract methods that it overrides.
Top#foo() is not public, protected, or declared with default access in the same package as Bottom. therefore Bottom#foo() does not override Top#foo(). So when you call top.foo() and Top#foo() is visible to the caller, Top#foo() gets called. I didn't find anywhere in the JLS that states that, given two visible methods for a type in scope which one will be called but it makes sense the one would be the reference-type method, but I can't find documentation to back me up.
author and iconoclast
Ireneusz Kordal wrote:
@Override is not the Eclipse warning, this is Java warning;
It really burns my butt to be "corrected" by folks who haven't even bothered to read a whole thread. Henry indeed reports that Eclipse -- which has its own Java compiler, and doesn't use the JDK one -- gives a warning for the case in which an attempted override fails because of visibility issues. javac doesn't warn about this -- it just compiles the code without remark.
Of course the @Override error is part of the Java spec. That's the whole point of @Override. I will try not to get too angry at your presumption here.
As for the "mistake in the name of the method", if you'd actually read this thread, I don't think you'd be making that comment either.
Steve Luke wrote:...
So when you call top.foo() and Top#foo() is visible to the caller, Top#foo() gets called. I didn't find anywhere in the JLS that states that, given two visible methods for a type in scope which one will be called but it makes sense the one would be the reference-type method, but I can't find documentation to back me up.
15.12.1 Compile-Time Step 1: Determine Class or Interface to Search
The first step in processing a method invocation at compile time is to figure out the name of the method to be invoked and which class or interface to check for definitions of methods of that name. There are several cases to consider, depending on the form that precedes the left parenthesis, as follows:
- If the form is MethodName, then there are three subcases:
- - If it is a simple name, that is, just an Identifier, then the name of the method is the Identifier. If the Identifier appears within the scope (§6.3) of a visible method declaration with that name, then there must be an enclosing type declaration of which that method is a member. Let T be the innermost such type declaration. The class or interface to search is T.
- - If it is a qualified name of the form TypeName . Identifier, then the name of the method is the Identifier and the class to search is the one named by the TypeName. If TypeName is the name of an interface rather than a class, then a compile-time error occurs, because this form can invoke only static methods and interfaces have no static methods.
- - In all other cases, the qualified name has the form FieldName . Identifier; then the name of the method is the Identifier and the class or interface to search is the declared type T of the field named by the FieldName, if T is a class or interface type, or the upper bound of T if T is a type variable.
So with top.foo(), the class to call the method on is the declared type of top, which is Top.
15.12.4 Runtime Evaluation of Method Invocation
18.104.22.168 Locate Method to Invoke
The strategy for method lookup depends on the invocation mode.
Otherwise, the invocation mode is interface, virtual, or super, and overriding may occur. A dynamic method lookup is used.
In this case, the invocation mode is virtual, since it is neither called on an interface or via a super. call.
The dynamic lookup process starts from a class S, determined as follows:
- If the invocation mode is interface or virtual, then S is initially the actual run-time class R of the target object. This is true even if the target object is an array instance. (Note that for invocation mode interface, R necessarily implements T; for invocation mode virtual, R is necessarily either T or a subclass of T.)
- If the invocation mode is super, then S is initially the qualifying type (§13.1) of the method invocation.
So we start looking for a version of Top#foo() in the runtime type of top, which is Bottom.
The dynamic method lookup uses the following procedure to search class S, and then the superclasses of class S, as necessary, for method m.
Let X be the compile-time type of the target reference of the method invocation.
- If class S contains a declaration for a non-abstract method named m with the same descriptor (same number of parameters, the same parameter types, and the same return type) required by the method invocation as determined at compile time (§15.12.3), then:
- - If the invocation mode is super or interface, then this is the method to be invoked, and the procedure terminates.
- - If the invocation mode is virtual, and the declaration in S overrides (§22.214.171.124) X.m, then the method declared in S is the method to be invoked, and the procedure terminates.
We might think that this means that Bottom#foo() should be called, because it shares th same descriptor as Top#foo(). But, as noted in my last post Bottom#foo() does not override Top#foo() because Top#foo() is not visible to Bottom. So this rule fails.
- - Otherwise, if S has a superclass, this same lookup procedure is performed recursively using the direct superclass of S in place of S; the method to be invoked is the result of the recursive invocation of this lookup procedure.
Which brings us back to executing Top#foo().
I get the output
when compiled and run from both javac/java version 1.6.0_16 and inside Eclipse.
Joined: Dec 26, 2008
I didn't realise this would unleash such a fury. But thank you all for your input.
FYI I have been using Java 1.6 and Netbeans to create the whole spiel.
I find this all very fascinating because I will be taking my SCJP in about 6 days.
In about 12 hours I'll get back to this thread and give it a try on Eclipse.
Joined: Dec 26, 2008
I just tried it out in Eclipse Europa.
Same 10/20 result.
And I tried the @Override annotation and got a good little message when it didn't compile :
This method isn't overriding the superclass method because :
the superclass method is private to a different package
So the method is not really overridden and polymorphism doesn't apply.
Very interesting. Thanks to Steve Luke and the others.