【问题标题】:Inheritance and Overloading methods with different argument data types in JavaJava中具有不同参数数据类型的继承和重载方法
【发布时间】:2015-11-07 00:26:58
【问题描述】:

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

【问题讨论】:

    标签: java inheritance polymorphism overloading overriding


    【解决方案1】:

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

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

    如果不是这样,您将无法完成任何有用的事情。您希望能够声明一个抽象类A,具体类B 和C 挂在上面,以不同的方式实现细节。但是您还希望能够声明A 类型的变量,而无需关心它们来自何处,以及它们实际上是B 类型还是C 类型。然后,您可以调用属于A 合同一部分的方法,它会做正确的事情:真正属于B 的东西将调用B 的实现,同样适用于C。

    至于为什么你最终会调用A 的calc 方法而不是D 的方法,这又是因为多态性的工作方式。变量的正式类型是A;所以当你调用.calc() 时,类型系统会:

    1. 在类A中找到最合适的方法来匹配调用在编译时;
    2. 查看A 和实际类型之间是否在运行时 被覆盖;
    3. 如果有,则调用覆盖的版本,如果没有,则调用A 的版本。

    但是您根本没有覆盖calc() 方法:您提供了具有不同签名的方法。所以在第 1 步(在编译时)类型系统找到了A.calc(double);在第 2 步(在运行时)中,它发现它没有在类层次结构中被覆盖;因此,在第 3 步(运行时)中,它会调用 A 的版本。

    重载在编译时根据形式类型解决;覆盖在运行时根据实际类型解析。

    【讨论】:

    • 我认为调用像这样的重载方法,首先将调用实际的类方法(在本例中为 D)(不是变量之一 -> A),如果不匹配合同转到父类等等......在这段代码中以这种方式工作:ideone.com/lNZlmH
    • @carvallo 在您链接到的代码中,这是一个 override,而不是 overload。覆盖具有相同的名称和相同的参数列表;重载具有相同的名称但参数列表不同。真正的重载方法是一种完全不同的方法。
    • 好的,我知道第二个例子是一个覆盖......所以,只是为了检查我是否理解得很好:在覆盖中,VM选择与对象合同匹配的方法(在运行时) (不是变量),并且在重载中,编译器会根据调用它的变量的类型来选择方法。如果它以这种方式工作,对我来说令人困惑的是,在我的示例中,当我在两种情况下检查对象的类型时,它都会返回 D,但是存储它们的两个变量都是 A 类型,这就是该方法的原因调用的是A类。
    【解决方案2】:

    这是因为这些方法是重载,而不是原始calc 方法的覆盖。因此,如果您使用类型为A 的引用,则只能看到最初属于A 的方法。所有其他方法都隐藏在对象中,就像您用新名称编写它们一样。

    因此,当编译器必须决定为每个计算调用哪个方法时,它并没有您认为的所有选项。它只有原始的calc(double),因此它将调用编译为“将值转换为双倍并调用calc(double)”。在编译时,它不知道实际的类不是A。它不能编译成代码说“在运行时检查是否有一个名为calc(int)的方法,如果有,使用它,如果没有,转换为double并使用calc(double)。它需要知道放在那里的指令在编译时。而在那个时候,它所知道的关于这个引用的只是它是一个A。

    针对 cme​​ts 进行编辑:

    编译器总是使用引用类型的协定选择将要调用的方法。即变量的类型,在本例中为 A。

    无论实际对象是否具有覆盖方法,都会发生这种情况。此时编译器不知道它。它的作用是告诉运行时环境:“当你到达这一点时,获取实际对象,并使用此签名运行方法:calc(double)”。

    所以,如果在运行时,实际对象也有calc(int)和calc(long)等方法命名为calc,没关系,因为编译器说“使用calc(double)”。

    现在,如果运行时对象有一个覆盖calc(double),运行时环境将采用它而不是原来的calc(double),因为这是覆盖的本质。

    总结一下:

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

    【讨论】:

    • 我认为调用像这样的重载方法,首先将调用实际的类方法(在本例中为 D)(不是变量之一 -> A),如果不匹配合同转到父类等等......在这段代码中以这种方式工作:ideone.com/lNZlmH
    • @carvallo 您的 ideone 示例中的代码包含 overridden 方法,而不是重载方法。编译器告诉它从父对象调用方法,但在运行时,实际对象有另一个方法替换它,以便使用一个方法。它在编译时不知道会有替换。
    • 好的,我知道第二个例子是一个覆盖......所以,只是为了检查我是否理解得很好:在覆盖中,VM选择与对象合同匹配的方法(在运行时) (不是变量),并且在重载中,编译器会根据调用它的变量的类型来选择方法。如果它以这种方式工作,对我来说令人困惑的是,在我的示例中,当我在两种情况下检查对象的类型时,它都会返回 D,但是存储它们的两个变量都是 A 类型,这就是该方法的原因调用的是A类。
    • @carvallo 我在回答中添加了一些信息,试图澄清这一点。关键是编译器既不选择重载也不选择覆盖。它只查看变量类中的严格定义。
    • 非常感谢@RealSkeptic!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-13
    • 1970-01-01
    • 2020-07-24
    • 2023-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多