Java中使用不同参数数据类型的继承和重载方法

时间:2015-08-13 21:01:51

标签: java inheritance polymorphism overloading override

当我分析一个与重载和继承相关的简单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

2 个答案:

答案 0 :(得分:2)

您的主要问题似乎是为什么类型输出与正式类型不匹配。这完全是故意的,它使得面向对象的编程变得如此强大。

在实例上调用方法时,运行时系统会查看实例的实际类型,并根据其实际类型而不是正式类型查找要调用的方法。

如果情况并非如此,那么您将无法完成任何有用的工作。您希望能够声明一个抽象类A,其中包含具体的类BC,以不同的方式实现细节。但是你也希望能够声明A类型的变量,而不关心它们来自何处,以及它们是否实际上属于B类型或类型{{1} }。然后,您可以调用属于C合同的方法,并且它会做正确的事情:真正A的某些内容会调用B& #39;的实施,同样适用于B

至于为什么你最终调用C A方法而不是calc方法,这又是因为多态性的工作方式。变量的形式类型是D;因此,当您调用A时,类型系统将:

  1. 在类.calc()中找到最合适的方法,以便在编译时匹配调用;
  2. 查看A与实际类型在运行时是否已被覆盖;
  3. 如果有,则调用被覆盖的版本,如果没有,则调用A的版本。
  4. 但您根本没有覆盖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),因为这是覆盖的本质。

总结一下:

  1. 编译器只知道引用类型中存在的方法签名 - 在这种情况下是您的变量。
  2. 编译器放置指令“使用具有此特定签名的方法或任何覆盖(具有相同签名)。
  3. 运行时环境查看实际对象,并检查它具有哪种calc(double)。如果它有覆盖,它将使用它。如果它只有原件,它将使用它。