My problem is that I can't understand how method resolution works in the following case: Suppose, we have two packages, A
and B
. There are two classes, A
is placed within A
, B
within B
.
A:
package com.eka.IO.a;
import com.eka.IO.b.B;
public class A {
void foo() {
System.out.println("parent");
}
public static void main(String... args) {
B obj = new B();
obj.foo();
}
}
B:
package com.eka.IO.b;
import com.eka.IO.a.A;
public class B extends A {
public void foo() {
System.out.println("child");
}
}
The code above prints "child", which is perfectly OK. But if I change the method main the following way:
public static void main(String... args) {
A obj = new B();
obj.foo();
}
the code prints "parent", and I don't understand why. (obj
has runtime type B
, B
has a public method foo
)
Next, I change foo's visibility to public,
public class A {
public void foo() {
and the code prints "child" again.
As far as I know, instance methods are resolved at runtime, using the following principle:
In my example, in any of three cases, runtime class for obj
is always B
. B
's method foo
is always public. Why in the second case JVM calls A
's method?
Up:
Good answers, but still some things are unclear for me.
a) It's the compiler that checks whether a method overrides another method. (Hope, I'm right).
b) in case of A obj = new B();
the compiler generates the following code:
INVOKEVIRTUAL com/eka/IO/a/A.foo ()V
b1)if A's foo is declared without modifier (package visibility), then JVM calls A's method. b2)if A's foo is declared public, then JVM calls B's method.
The unclear thing is why in the second case INVOKEVIRTUAL actually calls B.foo. How does it know, that B overrides the method?
The class implementing the interface cannot change the visibility of the method (we cannot change it from public to protected).
No, we cannot override private or static methods in Java. Private methods in Java are not visible to any other class which limits their scope to the class in which they are declared.
No. We cannot override an interface method if it's visibility is not public. And if it has its visibility as public then you can override it with the same method signature (i.e., with the same access specifier public) whenever you implement the interface to any class.
You cannot override a private or static method in Java. If you create a similar method with same return type and same method arguments in child class then it will hide the super class method; this is known as method hiding. Similarly, you cannot override a private method in sub class because it's not accessible there.
The process is slightly different than you described it. First, Java will only make the methods that exist in the declared class and are visible at the current scope available. This is already done at compile time.
At runtime,
Now, the tricky part is "has it been overridden"?
A class can't override a method that is not visible to it. It can declare a method by the same name and with the same arguments, but this method is not considered to be overriding the original method. It's simply a new method, just like any other method that's defined in B but not in A.
If this was not so, then you could break the parent's class contract at a place where the author thought it should not be broken and therefore did not allow access to it.
So since the class did not override the method, you can only reference that method the same way you'd be able to reference any method declared in B that was not in A - only through a B reference.
Why doesn't the compiler prevent you from using names of methods that are already in the parent class, then?
Well, if you get a package, and the only information you have about it is what's in the classes' contracts, as written in its Javadoc, you won't even know about the existence of that method. All of a sudden, you write a method that, as far as you know, is unique, and you get a compilation error.
There is no reason to do that. What's not visible to you should not prevent you from freely naming your own methods. Thus it is allowed.
But if you want the compiler to prevent you from making mistakes like that, use the @Override
annotation whenever you are writing a method that is supposed to override a parent class's method. This way, the compiler will warn you if you are trying to override a method that is not part of the class's contract.
You're experiencing Method Shadowing. From Java Language Specification. Chapter 6. Names. 6.4. Shadowing and Obscuring. 6.4.1. Shadowing (emphasys mine):
Some declarations may be shadowed in part of their scope by another declaration of the same name, in which case a simple name cannot be used to refer to the declared entity
(...)
A declaration d is said to be visible at point p in a program if the scope of d includes p, and d is not shadowed by any other declaration at p.
(...)
A declaration d of a method named n shadows the declarations of any other methods named n that are in an enclosing scope at the point where d occurs throughout the scope of d.
Let's check if B#foo
overrides A#foo
. From 8.4.8.1. Overriding (by Instance Methods):
An instance method mC declared in or inherited by class C, overrides from C another method mA declared in class A, iff all of the following are true:
- A is a superclass of C.
- C does not inherit mA.
- The signature of mC is a subsignature (§8.4.2) of the signature of mA.
- One of the following is true:
- mA is public. (not your case)
- mA is protected. (not your case)
- mA is declared with package access in the same package as C (not your case since the classes are in different packages), and either C declares mC or mA is a member of the direct superclass of C.
- mA is declared with package access and mC overrides mA from some superclass of C (not your case because there should be another class between C and A that let's you override mA).
- mA is declared with package access and mC overrides a method m' from C (m' distinct from mC and mA), such that m' overrides mA from some superclass of C (not your case because there should be another class between C and A that let's you override mA).
So, B#foo
does not override A#foo
by any mean. With this in mind, when you call obj.foo()
then foo
will be obtained based on the class obj
is designated at compile time. The explanation of this part is explained at 15.12. Method Invocation Expressions
If you want to avoid this, mark your method with @Override
annotation in the subclass to make sure you're specifically overriding the desired method and not hiding it. If you get a compiler error when annotating your method, then you will know that you're not overriding such method but shadowing it.
As a result from this, you cannot override a method with default scope from a subclass that is in a different package than the parent class. Mark the method as protected
in the parent class or redesign your classes accordingly to avoid this scenario.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With