使用超类引用调用重载继承方法

17

我不理解这个Java行为。我有两个类:

class C1 {
    public void m1(double num) {
        System.out.println("Inside C1.m1(): " + num);
    }
}

class C2 extends C1 {
    public void m1(int num) {
        System.out.println("Inside C2.m1(): " + num);
    }
}

这是我的主要内容:

public class Main {

    public static void main(String[] args) {
        C1 c = new C2();
        c.m1(10);
    }
}

结果是:

Inside C1.m1(): 10.0

当我期望:

Inside C2.m1(): 10

此外,当我尝试完善代码语法时,我发现了这个问题:

在这里输入图片描述

为什么C2类中的另一个m1方法消失了?

我还检查了Main.class的字节码,发现了这个:

Compiled from "Main.java"
public class com.company.Main {
  public com.company.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class com/company/C2
       3: dup
       4: invokespecial #3                  // Method com/company/C2."<init>":()V
       7: astore_1
       8: aload_1
       9: ldc2_w        #4                  // double 10.0d
      12: invokevirtual #6                  // Method com/company/C1.m1:(D)V
      15: return
}

这段字节码告诉我它将调用C1.m1 (D)V (第12行)。

为什么是C1的方法?我正在尝试理解这个行为。


请注意,将 @Override 添加到第二个 m1 将防止这种情况在现实生活中发生 :) - Navin
可能是Java重载和继承规则的重复问题。 - Raedwald
10个回答

16

你的两个名为 m1 的方法没有相同的签名; 超类中的一个参数类型是 double,子类中的一个参数类型是 int。这意味着编译器将基于变量的编译时类型(即C1)选择要调用的方法签名,并调用 m1(double)。由于在运行时类 C2 没有覆盖 m1(double) 的版本,因此将调用来自 C1 的版本。

规则是方法签名基于编译时类型在编译时计算;方法调用在运行时基于匹配签名分派。


所以,我必须进行强制转换吗?还是创建C2 c = new C2(); - Robert
@Rob3 这个问题没有解释 C2 中的方法 m1 是重载而不是被覆盖。请看我的答案,有详细的解释。 - Chetan Kinger
@Rob3 你必须执行其中一项操作才能调用 m1(int)。需要改变目标的编译时类型为 C2 - chrylis -cautiouslyoptimistic-
问题标题说的是重载行为而不是覆盖。 - Robert

9

这是因为参数的原因。你调用的方法是一个带有双重参数的方法。C2内部的m1并没有覆盖它,而是进行了重载。

如果你想要调用C2中的m1,你需要转换引用以使编译器接受你所做的操作。


1
这就是原帖中所说的。他正在方法重载。 - Alboz
他期望被调用的是重载方法吗?我不认为他那么蠢。 - Distjubo
我尝试调用另一个方法,但它没有出现。同时我认为C2有两个方法:第一个是m1(double),第二个是m1(int)。当我调用m1(10)并期望调用m1(int)时,却没有被调用。 - Robert
要调用另一个方法,您必须对对象进行强制转换,示例代码:((C2)c).m1(10); - Distjubo
另一种方法是由于自动类型转换而被调用。 - Distjubo
显示剩余8条评论

8
你看到的输出为 Inside C1.m1(): 10.0 而不是 Inside C1.m1(): 10 或者 Inside C2.m1(): 10.0 的原因是:
  1. 你没有在 C2 中覆盖方法 m1。你是将从 C1 继承的 m1(double) 方法重载为 m1(int)
  2. C2 类现在有两个 m1 方法:一个从 C1 继承并具有签名 m1(double),另一个在 C2 中重载并具有签名 m1(int)
  3. 当编译器看到调用 c.m1(10) 时,它会根据引用类型解析此调用。由于引用类型为 C1,编译器将该调用解析为 C1 中的 m1(double)
  4. 在运行时,JVM 将调用 C2 中继承自 C1m1(double)。 (如第 2 点所述)
< p >有两种方式可以调用 m1(int) 方法:

((C2)c).m1(10);

或者

C2 c = new C2(); c.m1(10);


6

Java使用静态类型进行方法调度,而您的变量c的类型是C1,因此m1(int)不可见,并且您的10被强制转换为double


这对我来说似乎是最简洁和最好的答案。 - Alboz
这个答案是最简单的解释。通常情况下,在C1对象上调用m1(int)是不正确的,即使实例是C2,因为一般来说,C1类型的对象没有这样的方法。 - Patrick White

4

这两种方法的签名不同。

public void m1(double num) 
public void m1(int num)

所以在这种情况下没有覆盖。现在当你说
    C1 c = new C2();
    c.m1(10);

在编译时,它会检查引用的类型是否为C1,该类型具有与10 [int转换为double]兼容的public void m1(double num)方法。因此,int被提升为double,并调用相应的方法(这也是您在字节码中看到的内容)。


3
如果您调用c.m1(10.0),它将像您最初预期的那样调用祖先的方法。
在您的示例中,您正在执行方法重载(即添加具有相同名称但不同签名的更多方法),而不是方法覆盖(即通过使用相同签名重新声明祖先方法的实现,在后代中更改其实现,也称为相同名称和结果类型以及方法参数的相同类型 - 参数名称不应重要)。

2

通过查看您的代码,您没有利用继承来获得所需的答案。您需要更改这一行。

C1 c = new C2();

to

C2 c = new C2();

2

您无法看到C2的方法,因为您的实例变量声明为C1,并且它们没有相同的签名。您在一个方法中有双参数,在第二个方法中有int类型,这使得它们对于JVM来说是完全不同的方法(因此在这里不会有继承)。

因此,如果您在C1方法中有int类型,则需要在C2方法中也有int类型,然后JVM将按您所需运行C2中的方法。

此外,您可以将变量转换为C2类型,然后就能够访问C2的方法了。


1

由于C1.m1(double num)是公共方法,它被C2继承了。因此你的C2也有了一个方法m1(double num),这就是为什么它被调用了。main()实际上是在调用C2.m1(double num)

注意:现在在类C2中有两个重载的方法 - m1(int num)m1(double num)C2.m1(int num)是不同于C2.m1(double num)的方法。


0

Java会选择最具体的适用类型。在这种情况下,m1(int)不适用。 强调持有同一类别(c1)或任何子类别(c2)对象的类的引用和方法名称以及参数列表。

因为double可以赋值给int,但反过来不行,所以你的带有double参数的方法被调用了。

因此,在方法调用时需要考虑很多事情。

是的,对于您的情况,您的主类应该像这样:

        public static void main(String[] args) {
            C1 c = new C2();
            c.m1(10);
            ((C2) c).m1(10);
    //or
            C2 cobj = new C2();
            cobj.m1(10);
        }


**OutPut**
Inside C1.m1(): 10.0
Inside C2.m1(): 10
Inside C2.m1(): 10

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接