Reputation: 110
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 Moe {
public void print(Moe p) {
System.out.println("Moe 1");
}
}
class Larry extends Moe {
public void print(Moe p) {
System.out.println("Larry 1");
}
public void print(Larry l) {
System.out.println("Larry 2");
}
}
class Curly extends Larry {
public void print(Moe p) {
System.out.println("Curly 1");
}
public void print(Larry l) {
System.out.println("Curly 2");
}
public void print(Curly b) {
System.out.println("Curly 3");
}
}
class Overloading {
public static void main (String [] args) {
Larry stooge1 = new Curly();
Moe stooge2 = new Larry();
Moe stooge3 = new Curly();
Curly stooge4 = new Curly();
Larry stooge5 = new Larry();
stooge1.print(new Moe());
stooge1.print(new Curly());
stooge1.print(new Larry());
stooge2.print(new Curly());
stooge3.print(new Curly());
stooge3.print(new Larry());
stooge5.print(new Curly());
}
}
I think I get the first one but on the others I am completely lost. This is how I solved the first one:
at runtime the type of stooge1
is Curly
, so we're calling the print method of Curly. Because we pass an object of type Moe
to print, the corresponding print method with argument type Moe
is run in Curly
. The output of this method is Curly 1
, the correct answer.
However, when I apply this technique to the following lines I end up with the wrong answers. Can someone explain me how exactly this concept works in Java?
The correct output of the code is:
Curly 1
Curly 2
Curly 2
Larry 1
Curly 1
Curly 1
Larry 2
Upvotes: 3
Views: 1260
Reputation: 124225
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)
.
Upvotes: 7
Reputation: 159096
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.
Upvotes: 0
Reputation: 726579
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.
Upvotes: 1
Reputation: 48577
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 =\
Upvotes: -1