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
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)
}
}
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.
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.
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.
In inheritance, a base class object can refer to an instance of derived class.
This is how Top top = sub;
works well.
For
System.out.println(top.f(obj));
:The
top
object tries to use thef()
method of theSub
class. Now there being twof()
method in theSub
class, the type check is made for the passed argument. Since the type isObject
the secondf()
method ofSub
class gets invoked.For
System.out.println(top.f(str));
:You can interpret the same as (1) i.e. the type is
String
so the firstf()
function gets invoked.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 theSub
class, here also the type check is made for the argument passed. Since the argument passed is of typeObject
, the secondf()
method gets invoked.For
System.out.println(sub.f(str));
:Similar to 3., here the type passed is
String
so the firstf()
function ofSub
class gets invoked.
Hope this helps.
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
来源:https://stackoverflow.com/questions/5658553/question-about-java-overloading-dynamic-binding