Question about Java overloading & dynamic binding

user482594 picture user482594 · Apr 14, 2011 · Viewed 7.3k times · Source

In the code below, how does first and second print statements print out SubObj?? Do top and sub point to the same Sub class?

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";}
    public String f(Object o) {return "SubObj";}
}

public class Test {
    public static void main(String[] args) {  
        Sub sub = new Sub();
        Top top = sub;
        String str = "Something";
        Object obj = str;


        System.out.println(top.f(obj));
        System.out.println(top.f(str));
        System.out.println(sub.f(obj));
        System.out.println(sub.f(str));
    }
}

Above code returns below result.

SubObj
SubObj
SubObj
Sub

Answer

user711807 picture user711807 · Apr 25, 2011

Since you already understand case 1, 3, and 4, let's tackle case 2.

(Please note - I am by no means an expert on the inner workings of the JVM or compilers, but this is how I understand it. If someone reading this is a JVM expert, feel free to edit this answer of any discrepancies you may find.)

A method in a subclass that has the same name but a different signature is known as method overloading. Method overloading uses static binding, which basically means that the appropriate method will be forced to be "chosen" (i.e. bound) at compile-time. The compiler has no clue about the runtime type (aka the actual type) of your objects. So when you write:

                         // Reference Type  // Actual Type
    Sub sub = new Sub(); // Sub                Sub
    Top top = sub;       // Top                Sub

the compiler only "knows" that top is of type Top (aka the reference type). So when you later write:

    System.out.println(top.f(str)); // Prints "subobj"

the compiler "sees" the call 'top.f' as referring to the Top class's f method. It "knows" that str is of type String which extends Object. So since 1) the call 'top.f' refers to Top class's f method, 2) there is no f method in class Top that takes a String parameter, and 3) since str is a subclass of Object, the Top class's f method is the only valid choice at compile time. So the compiler implicitly upcasts str to its parent type, Object, so it can be passed to Top's f method. (This is in contrast to dynamic binding, where type resolution of the above line of code would be deferred until runtime, to be resolved by the JVM rather than the compiler.)

Then at runtime, in the above line of code, top is downcast by the JVM to it's actual type, sub. However, the argument str has been upcast by the compiler to type Object. So now the JVM has to call an f method in class sub that takes a parameter of type Object.

Hence, the above line of code prints "subobj" rather than "sub".

For another very similar example, please see: Java dynamic binding and method overriding

Update: Found this detailed article on the inner workings of the JVM:

http://www.artima.com/underthehood/invocationP.html

I commented your code to make it more clear what's going on:

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";} // Overloading = No dynamic binding
    public String f(Object o) {return "SubObj";} // Overriding = Dynamic binding
}

public class Test {
    public static void main(String[] args) {  

                                  // Reference Type     Actual Type
        Sub sub = new Sub();      // Sub                Sub
        Top top = sub;            // Top                Sub
        String str = "Something"; // String             String
        Object obj = str;         // Object             String

                                        // At Compile-Time:      At Run-Time:
        // Dynamic Binding
        System.out.println(top.f(obj)); // Top.f (Object)   -->  Sub.f (Object)

        // Dynamic Binding
        System.out.println(top.f(str)); // Top.f (Object)   -->  Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(obj)); // Sub.f (Object)        Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(str)); // Sub.f (String)        Sub.f (String)
    }
}