Question about Java overloading & dynamic binding
Asked Answered
S

6

19

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
Stammer answered 14/4, 2011 at 4:29 Comment(2)
Now I got how first line works, but how come does the second line prints out SubObj, even if the input call was top.f(str) where str is a type of String?Stammer
I posted an answer, did you look at it. It should solve your doubts. To summarize think from the point of "Type Check" for the passed argument. Please accept the answer if you find it helpful..Birddog
S
16

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)
    }
}
Singletary answered 25/4, 2011 at 0:45 Comment(0)
E
8

This is because all method calls in Java are virtual (by default).

That is, the resolution starts at the actual object (not the type of expression) and "works up" the inheritance chain (per the actual objects type) until the first matching method is found. Non-virtual methods would start at the type of expression. (Marking a method as final makes it non-virtual.)

However, the exact method signature is determined at compile-time (Java does not support multi-dispatch, single-dispatch only varies at run-time based upon the receiver object) -- this explains why Sub.f(String) results in "Sub", for instance while Top.f(String) "binds" to the method matching Top.f(Object) even if invoked upon a sub-type of Top. (It was the best eligible signature determined at compile-time). The virtual dispatch itself, is the same.

Happy coding.

Ellord answered 14/4, 2011 at 4:37 Comment(0)
W
2

This has to do with the apparent type of the object. At compile time Java does its type checking based on the type that you declare your object to be rather than the specific type that you instantiate.

You have a type Top with a method f(Object). So when you say:

 System.out.println(top.f(obj));

The Java compiler only cares that the object top is of type Top and the only method available takes an Object as a parameter. At run time it then calls the f(Object) method of the actual instantiated object.

The next call is interpreted in the same way.

The next two calls are interpreted as you would expect.

Wilcox answered 14/4, 2011 at 4:39 Comment(0)
M
1

Yep, they both point to Sub class. The issue is that top knows only about

f(Object o)

and it can call only that signature.

But sub knows both signatures and have to select by parameter type.

Multipurpose answered 14/4, 2011 at 4:36 Comment(0)
B
1

In inheritance, a base class object can refer to an instance of derived class.

This is how Top top = sub; works well.

  1. For System.out.println(top.f(obj));:

    The top object tries to use the f() method of the Sub class. Now there being two f() method in the Sub class, the type check is made for the passed argument. Since the type is Object the second f() method of Sub class gets invoked.

  2. For System.out.println(top.f(str));:

    You can interpret the same as (1) i.e. the type is String so the first f() function gets invoked.

  3. For System.out.println(sub.f(obj));:

    This is simple as you are calling the method of Sub class itself. Now since there are two overloaded method in the Sub class, here also the type check is made for the argument passed. Since the argument passed is of type Object, the second f() method gets invoked.

  4. For System.out.println(sub.f(str));:

    Similar to 3., here the type passed is String so the first f() function of Sub class gets invoked.

Hope this helps.

Birddog answered 14/4, 2011 at 4:46 Comment(1)
For the second one, the actual output is 'SubObj' not, 'Sub'Stammer
A
0

in

Sub sub = new Sub();
Top top = sub;

you made an instance of sub, then up casted it to top, which makes it only know about methods that exist in top. the method that exists in top is public String f(Object o) {return "Top";}

now also that method is overloaded by sub so it will get called when you make an instance of sub and upcast it to top.

another way to put this is that you got

sub type as the apparent type, but top as the actual type, because you assigned sub to top. you will call the methods in the apparent type if it overloads the actual type, but you wont be able to call any method that doesnt exist in the actual type

Aruspex answered 14/4, 2011 at 4:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.