在下面的代码中,第一个和第二个print语句如何打印出SubObj??top和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是扩展了Object的String类型。所以自从1)call 'top。f'指的是Top类的f方法,2)在Top类中没有接受String参数的f方法,3)因为str是Object的子类,所以Top类的f方法是编译时唯一有效的选择。因此,编译器隐式地将str向上转换为它的父类型Object,这样它就可以传递给Top的f方法。(这与动态绑定相反,在动态绑定中,上述代码行的类型解析将延迟到运行时,由JVM而不是编译器解析。)
然后在运行时,在上面的代码行中,JVM将top向下转换为它的实际类型sub。然而,参数str已被编译器向上转换为Object类型。所以现在JVM必须调用类sub中的f方法,该方法接受Object类型的参数。
因此,上面这行代码输出的是" subbobj "而不是"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中的所有方法调用都是虚拟的(默认情况下)。
也就是说,解析从实际对象(而不是表达式的类型)开始,并"向上处理"继承链(每个实际对象类型),直到找到第一个匹配方法。非虚方法将从表达式的类型开始。(将方法标记为final
使其非虚拟)
然而,确切的方法签名是在编译时确定的(Java不支持多调度,单调度只在运行时基于接收对象而变化)——这解释了为什么Sub.f(String)
结果为"Sub",例如,当Top.f(String)
"绑定"到匹配Top.f(Object)
的方法时,即使在Top的子类型上调用。(这是在编译时确定的最佳合格签名)。虚拟调度本身也是一样的。
幸福的编码。
这与对象的表面类型有关。在编译时,Java根据您声明的对象类型而不是实例化的特定类型进行类型检查。
你有一个类型Top和一个方法f(Object)。所以当你说:
System.out.println(top.f(obj));
Java编译器只关心对象top是top类型,并且唯一可用的方法接受object作为参数。然后在运行时调用实际实例化对象的f(Object)方法。
下一个调用用同样的方式解释。
接下来的两个调用被解释为您所期望的。
是的,它们都指向Sub
类。问题是top
只知道
f(Object o)
,它只能调用那个签名
但是sub
知道这两个签名,所以必须根据参数类型进行选择
在继承中,基类对象可以引用派生类的实例。
这就是Top top = sub;
的工作原理。
-
For
System.out.println(top.f(obj));
:top
对象尝试使用Sub
类的f()
方法。现在在Sub
类中有两个f()
方法,对传递的参数进行类型检查。因为类型是Object
,所以调用Sub
类的第二个f()
方法。 -
For
System.out.println(top.f(str));
:你可以解释为(1),即类型是
String
,所以第一个f()
函数被调用。 -
For
System.out.println(sub.f(obj));
:这很简单,因为您正在调用
Sub
类本身的方法。现在,由于Sub
类中有两个重载方法,这里也对传递的参数进行类型检查。由于传递的参数类型为Object
,因此调用第二个f()
方法。 -
For
System.out.println(sub.f(str));
:类似于3。,这里传递的类型是
String
,所以Sub
类的第一个f()
函数被调用。
希望这对你有帮助。
in
Sub sub = new Sub();
Top top = sub;
创建了sub的实例,然后将其向上强制转换为top,这使得它只知道top中存在的方法。top中存在的方法是public String f(Object o) {return "Top";}
现在这个方法也被sub重载了,所以当你创建一个sub实例并将其上推到top时,它会被调用。
另一种说法是你得到
sub类型作为表面类型,但top作为实际类型,因为您将sub分配给了top。如果它重载了实际类型,你将调用表面类型中的方法,但是你不能调用实际类型