当我分析一个与重载和继承相关的简单java代码时,我希望收到一个重载匹配参数数据类型的输出。但它并没有这样做。
代码:
class A {
public int calc (double num){
System.out.println("calc A");
return (int)(num+1);}
}
class B extends A{
public int calc (long num){
System.out.println("calc B");
return (int)(num+2);}
}
class C extends B{
public int calc (int num){
System.out.println("calc C");
return num+3;}
}
class D extends C{
public int calc (float num){
System.out.println("calc D");
return (int)(num+4);}
}
class Program{
public static void main(String[] args){
int num1=10;
long num2 = num1;
Object o1 = num1;
System.out.println("num1 Type: "+o1.getClass().getName());
Object o2 = num2;
System.out.println("num2 Type: "+o2.getClass().getName());
A a1=new D();
A a2=new D();
System.out.println("a1 Type: "+a1.getClass().getName());
System.out.println("a2 Type: "+a2.getClass().getName());
int result = a1.calc(num1)+a2.calc(num2);
System.out.println("Number: "+result);
}
}
输出:
num1 Type: java.lang.Integer
num2 Type: java.lang.Long
a1 Type: D
a2 Type: D
calc A
calc A
Number: 22
我在这里测试代码: ideone
答案 0 :(得分:2)
您的主要问题似乎是为什么类型输出与正式类型不匹配。这完全是故意的,它使得面向对象的编程变得如此强大。
在实例上调用方法时,运行时系统会查看实例的实际类型,并根据其实际类型而不是正式类型查找要调用的方法。
如果情况并非如此,那么您将无法完成任何有用的工作。您希望能够声明一个抽象类A
,其中包含具体的类B
和C
,以不同的方式实现细节。但是你也希望能够声明A
类型的变量,而不关心它们来自何处,以及它们是否实际上属于B
类型或类型{{1} }。然后,您可以调用属于C
合同的方法,并且它会做正确的事情:真正A
的某些内容会调用B
& #39;的实施,同样适用于B
。
至于为什么你最终调用C
A
方法而不是calc
方法,这又是因为多态性的工作方式。变量的形式类型是D
;因此,当您调用A
时,类型系统将:
.calc()
中找到最合适的方法,以便在编译时匹配调用; A
与实际类型在运行时是否已被覆盖; A
的版本。但您根本没有覆盖A
方法:您提供了具有不同签名的方法。所以在第1步(编译时)类型系统找到calc()
;在步骤2中(在运行时),它发现它没有在类层次结构中进一步被覆盖;因此,在步骤3(运行时)中,它会调用A.calc(double)
的版本。
根据正式类型在编译时解决重载;根据实际类型在运行时解析覆盖。
答案 1 :(得分:0)
这是因为这些方法是重载,而不是原始calc
方法的覆盖。因此,如果您使用的类型为A
,那么所有可以看到的方法都是最初属于A
的方法。所有其他方法都隐藏在对象中,就像您用新名称编写它们一样。
因此,当编译器必须决定为每个计算调用哪个方法时,它没有您认为具有的所有选项。它只有原始calc(double)
,因此它将调用编译为“将值转换为double并调用calc(double)
”。在编译时,它不知道实际的类不是A
。它无法编译成代码“在运行时检查是否存在名为calc(int)
的方法,如果是,请使用它,否则,转换为double并使用calc(double)
。它需要知道什么在编译时把它放到那里的说明。当时,它所知道的所有引用都是A
。
编辑以回应评论:
编译器总是选择使用引用类型的契约调用哪个方法。也就是说,变量的类型,在这种情况下是A。
无论实际对象是否具有覆盖方法,都会发生这种情况。此时编译器不知道它。它的作用是告诉运行时环境:“当你到达这一点时,取出实际的对象,并运行带有此签名的方法:calc(double)
”。
因此,如果在运行时,实际对象也有calc(int)
和calc(long)
以及其他名为calc
的方法,那没关系,因为编译器说“使用{{1 }}”。
现在,如果运行时对象具有覆盖 calc(double)
,则运行时环境将采用该代替原始calc(double)
,因为这是覆盖的本质。
总结一下:
calc(double)
。如果它有覆盖,它将使用它。如果它只有原件,它将使用它。