既然你已经了解了案例 1、3 和 4,让我们来处理案例 2。
(请注意 - 我绝不是 JVM 或编译器内部工作的专家,但这就是我的理解。如果阅读本文的人是 JVM 专家,请随时编辑您可能发现的任何差异的答案.)
子类中具有相同名称但签名不同的方法称为方法重载。方法重载使用静态绑定,这基本上意味着在编译时将强制“选择”(即绑定)适当的方法。编译器不知道对象的运行时类型(也就是实际类型)。所以当你写:
// Reference Type // Actual Type
Sub sub = new Sub(); // Sub Sub
Top top = sub; // Top Sub
编译器只“知道” top 是 Top 类型(又名引用类型)。所以当你以后写:
System.out.println(top.f(str)); // Prints "subobj"
编译器“看到”调用“top.f”是指 Top 类的 f 方法。它“知道” str 是扩展 Object 的 String 类型。所以因为 1) 调用 'top.f' 指的是 Top 类的 f 方法,2) Top 类中没有 f 方法接受 String 参数,以及 3) 因为 str 是 Object 的子类,所以 Top 类的 f 方法是编译时唯一有效的选择。所以编译器隐式地将 str 向上转型为它的父类型 Object,因此它可以传递给 Top 的 f 方法。(这与动态绑定形成对比,其中上述代码行的类型解析将推迟到运行时,由 JVM 而不是编译器解析。)
然后在运行时,在上面的代码行中,top 被 JVM 向下转换为它的实际类型 sub。但是,参数 str 已被编译器向上转换为 Object 类型。所以现在 JVM 必须在 sub 类中调用一个 f 方法,该方法接受一个 Object 类型的参数。
因此,上面的代码行打印“subobj”而不是“sub”。
对于另一个非常相似的示例,请参阅:Java 动态绑定和方法覆盖
更新:找到这篇关于 JVM 内部工作原理的详细文章:
http://www.artima.com/underthehood/invocationP.html
我评论了您的代码,以使其更清楚发生了什么:
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)
}
}