Two important things for method invocations.
You have two times : the compilation time and the runtime.
And rules are not the same between these two times.
at compile time the compiler has to definite statically which exact signature of method is called to compile fine.
This binding is static as the compiler doesn't matter of the concrete instance on which the method is invoked and it is the same thing for the parameters passed to the method.
The compiler doesn't rely on the effective types as at runtime the effective types could change during the execution flow.
So the compiler searches among available methods for a declared type, which is the more specific method according to the declared types of parameters passed to it.
at runtime an instance method from a class or from another one will be used according to the effective instance on which the method is invoked.
But the invoked method has to respect the signature specified at compile time.
1)For the first case :
Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(hybrid.play(foxi)); // Output number 1
- First time (compîle time) :
For a Dog instance, the compiler has to find the most specific method play()
with as parameter a variable with the Fox declared type.
In Dog class, a single method play()
with a compatible signature exist :
public String play(Dog d) {
So this signature is used for the binding : String play(Dog d)
.
About the bark()
method, it is much obvious as there is only one bark() method signature .
So we have no ambiguity on the method that is bound at compile time
At runtime the String play(Dog d)
method of the concrete instance is invoked.
The hybrid
variable refers to an instance of Fox but Fox doesn't override
String play(Dog d)
. Fox defines a play() method but with another signature :
public String play(Fox f) {
So the JVM invokes the public String play(Dog d) {
method of Dog.
And then it invokes the method of the effective type of d
when d.bark()
is executed and d
refers to a Fox
instance.
So "WuffRingding" is output.
2) For the second case :
Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(foxi.play(hybrid)); // Output number 2
- First time (compile time) :
For a Fox
instance, the compiler has to find the most specific method play()
with as parameter a variable with the Dog
declared type.
In Fox
class, two play()
methods with a compatible parameter exist :
public String play(Dog d) { // inherited from the parent class
public String play(Fox f) { // declared in Fox
The compiler has to choose the more specific method for the call context of the method
It identifies a method more specific that the other one for a Dog
declared type parameter : public String play(Dog d)
.
So the compiler binds the play()
method invocation to public String play(Dog d)
when it compiles the class.
At runtime the String play(Dog d)
method of the concrete instance is invoked.
As for the first case, the foxi
variable refers to an instance of Fox but Fox doesn't override String play(Dog d)
.
So the JVM invokes the public String play(Dog d)
method of Dog.
And then it invokes the method of the effective type of f
when f.bark()
is executed and f
refers to a Fox
instance.
So "WuffRingding" is again output.
To avoid this kind of surprise you should add @Override
in the methods designed to override a parent class method :
For example :
@Override
public String play(Fox f) {
return "Ringding" + f.bark();
}
If the method doesn't override effectively a play(Fox f)
method in the hierarchy, the compiler will complain about it.