I am practicing for a test and I came across this exercise about overloading and static and dynamic binding. The output of the following code is asked:
class
Here is what's going on:
stooge1.print(new Moe()); // All three have overload for Moe,
// so the overload from the dynamic type of stooge1 gets called
stooge1.print(new Curly()); // Compiler thinks stooge1 is Larry,
// so it does not know that it has an overload for Curly.
// It uses the overload for Larry instead, because Curly is a Larry
stooge1.print(new Larry()); // Same logic as above applies.
stooge2.print(new Curly()); // Compiler thinks stooge2 is Moe, so its only overload
// is for Moe. Since the dynamic type is Larry, first overload is invoked
The remaining three cases can be solved by applying the same logic as above.
For #1, #2, #3 stooge1
is declared a Larry
, so only methods available to Larry
can be called.
Passing a Moe
will call print(Moe)
. Since the actual class is a Curly
, it prints "Curly 1".
Passing a Larry
will call print(Larry)
, since that is a better match that print(Moe)
. This will print "Curly 2".
Passing a Curly
will also call print(Larry)
. Note that print(Curly)
is unknown to stooge1
, so it cannot be selected by the compiler. Therefore it also prints "Curly 2".
Now try to figure out the rest.
So this is a super confusing and awful example of something you should never do. The declared type of the variables matters for what signature methods have. So Larry
doesn't have method that accepts a Curly
, so the compiler considers the argument a Larry
. But it gets dispatched to Curly
's version of the method.
So yeah, never ever do this =\
Static binding happens at compilation time and dynamic binding at runtime.
Static binding is responsible for selecting signature (name and argument types) of method which should be executed. It uses
Compiler selects signature from variable type on which method is invoked, so Object o = "abc";
will not allow you to invoke o.substring(1,2);
because compiler will not be able to find substring(int, int)
signature in Object
class (which is type of o
variable on which substring
method was invoked).
Dynamic binding is responsible for finding and invoking code of method selected by static binding at compilation time. It will try to find code of method in type of actual instance held by variable. In other words if you have Animal a = new Cat(); a.makeSound();
you can expect to get as result "Mew"
because at runtime JVM will search and invoke code of makeSound
starting from Cat
class. If implementation will not be provided in that class JVM will search for it in ancestor(s) until it finds one from which it was inherited.
I renamed classes and variables in your example a little to hopefully make it more readable:
class A {
public void print(A a) {
System.out.println("A.print(A)");
}
}
class B extends A {
public void print(A a) {
System.out.println("B.print(A)");
}
public void print(B b) {
System.out.println("B.print(B)");
}
}
class C extends B {
public void print(A a) {
System.out.println("C.print(A)");
}
public void print(B b) {
System.out.println("C.print(B)");
}
public void print(C c) {
System.out.println("C.print(C)");
}
}
class OverloadingDemo {
public static void main (String [] args) {
A ab = new B();
A ac = new C();
B bb = new B();
B bc = new C();
bc.print(new A());
bc.print(new C());
bc.print(new B());
ab.print(new C());
ac.print(new C());
ac.print(new B());
bb.print(new C());
}
}
(variable naming -> variable of type X
holding instance of type Y
is named xy
).
So, when we execute
bc.print(new A());
print
method signature available in class B
which can handle instance of type A
. In this case it will be print(A)
. C
(since this is type of instance held by bc
variable) which means we will see C.print(A)
.Similarly in case of bc.print(new C());
print
method for C
argument available in B
class, which for C
is print(B)
(since there is no print(C)
there and B is closest supertype). C
class (since this is instance which bc
holds).So it will invoke C.print(B)
.