关于Java重载和动态绑定的问题

在下面的代码中,第一个和第二个打印语句如何打印出SubObj? 顶部和子指向同一个Sub类吗?

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)); } } 

上面的代码返回结果。

 SubObj SubObj SubObj Sub 

既然您已经了解案例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是String类型,它扩展了Object。 因此,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必须在class sub中调用一个带有Object类型参数的f方法。

因此,上面的代码行打印“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) } } 

这是因为Java中的所有方法调用都是虚拟的 (默认情况下)。

也就是说,分辨率从实际对象 (不是表达式类型 )开始,并“继续”inheritance链(根据实际对象类型 ),直到找到第一个匹配方法。 非虚方法将从表达式开始 。 (将方法标记为final是非虚拟的。)

但是, 确切的方法签名是在编译时确定的 (Java不支持多分派,单分派仅在运行时根据接收者对象而变化) – 这解释了为什么Sub.f(String)导致“ Sub“,例如Top.f(String) ”绑定“匹配Top.f(Object)的方法,即使在Top类型的子类型上调用。 (这是在编译时确定的最佳合格签名)。 虚拟调度本身也是一样的。

快乐的编码。

这与对象的表观类型有关。 在编译时,Java根据您声明对象的类型而不是您实例化的特定类型进行类型检查。

你有一个类型Top与方法f(对象)。 所以当你说:

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

Java编译器只关心对象top是Top类型,唯一可用的方法是将Object作为参数。 在运行时,它然后调用实际实例化对象的f(Object)方法。

下一个调用以相同的方式解释。

接下来的两个调用将被解释为您所期望的。

是的,他们都指向Sub类。 问题是top只知道

 f(Object o) 

它只能调用那个签名。

但是sub知道两个签名并且必须通过参数类型进行选择。

在inheritance中,基类对象可以引用派生类的实例。

这就是Top top = sub; 效果很好。

  1. 对于System.out.println(top.f(obj));

    top对象尝试使用Sub类的f()方法。 现在Sub类中有两个f()方法,对传递的参数进行类型检查。 由于类型是Object因此调用Sub类的第二个f()方法。

  2. 对于System.out.println(top.f(str));

    你可以解释为(1),即类型是String因此第一个f()函数被调用。

  3. 对于System.out.println(sub.f(obj));

    这很简单,因为您正在调用Sub类本身的方法。 既然Sub类中有两个重载方法,这里也会对传递的参数进行类型检查。 由于传递的参数是Object类型,因此调用第二个f()方法。

  4. 对于System.out.println(sub.f(str));

    与3.类似,这里传递的类型是String因此调用Sub类的第一个f()函数。

希望这可以帮助。

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

你创建了一个sub的实例,然后将它转换为top,这使得它只知道顶部存在的方法。 top中存在的方法是public String f(Object o) {return "Top";}

现在,该方法也被sub重载,因此当您创建sub的实例并将其向上转发时它将被调用。

另一种方法就是你得到了

sub type作为表观类型,但top作为实际类型,因为你指定了sub to top。 如果它重载实际类型,你将调用表观类型的方法,但你不能调用实际类型中不存在的任何方法