【问题标题】:Calling overloaded inherited methods using super class reference使用超类引用调用重载的继承方法
【发布时间】:2015-07-04 08:01:39
【问题描述】:

我不理解这种 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的方法?我正在尝试理解这种行为。

【问题讨论】:

标签: java oop inheritance syntax overloading


【解决方案1】:

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

规则是方法 signatures 在编译时根据编译时类型计算;方法调用在运行时根据匹配的签名分派

【讨论】:

  • 所以,我必须强制转换吗?或创建C2 c = new C2();
  • @Rob3 这个问题没有说明C2 中的方法m1 是重载的,而不是overriden。详细解释见我的回答。
  • @Rob3 你必须做一个或另一个来调用m1(int)。将目标的编译时类型更改为C2
  • 问题标题说重载行为没有覆盖
【解决方案2】:

这是因为参数。您调用的方法是带有双参数的方法。 C2 内部的 m1 并没有覆盖它,而是重载它。

如果您想在 C2 中调用 m1,则必须强制转换引用,以便编译器接受您正在执行的操作。

【讨论】:

  • 这就是 OP 所说的。他正在重载方法。
  • 他期望调用 overloaded 方法吗?我觉得他没那么笨
  • 我尝试调用另一种方法,但它没有出现。我还认为 C2 首先有两种方法:m1(double) 和第二种方法:m1(int) 当我调用 m1(10) 并期望m1(int ) 将被调用。
  • 要调用其他方法,你必须强制转换对象,示例代码:((C2)c).m1(10);
  • @Rob3 阅读了 SRobertz 的答案。您可见的方法取决于变量 TYPE 而不是实际对象。
【解决方案3】:

您看到输出为Inside C1.m1(): 10.0 而不是Inside C1.m1(): 10Inside C2.m1(): 10.0 的原因是:

  1. 您没有覆盖C2 中的方法m1。您正在重载从 C1 继承的 m1(doube) 方法到 m1(int)
  2. C2 类现在有两个 m1 方法。一个是来自C1inherited 并具有签名m1(double),另一个是在C2 中重载并具有签名m1(int)
  3. 当编译器看到调用c.m1(10) 时,它会根据引用类型解析此调用。由于引用类型为C1,编译器将在C1 中解析对m1(double) 的调用。
  4. 在运行时,JVM 将在C2 中解析对m1(double) 的调用,这是从C1 继承的方法。 (如第 2 点所述)

有两种方法可以调用m1(int) 方法:

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

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

【讨论】:

    【解决方案4】:

    Java 对静态类型进行方法分派,而您的变量 c 的类型为 C1,因此 m1(int) 不可见,您的 10 被强制转换为 double

    【讨论】:

    • 这对我来说似乎是最短和最好的答案。
    • 这个答案是最简单的解释。一般来说,在 C1 对象上调用 m1(int) 是不正确的,即使实例是 C2,因为通常 C1 类型的对象没有这样的方法。
    【解决方案5】:

    两种方法的方法签名不同。

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

    所以在这种情况下没有覆盖。现在当你说

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

    在编译时,它的引用类型为C1,其方法public void m1(double num) 与10 [int in expand to double] 兼容。所以int提升为double,调用对应的方法(这也是你在字节码中看到的)。

    【讨论】:

      【解决方案6】:

      如果您调用 c.m1(10.0),它将按照您最初的预期调用祖先的方法。

      您在示例中进行方法重载(即添加更多具有相同名称和不同签名的方法)而不是方法覆盖(即通过使用相同的签名重新声明祖先方法在后代中的实现来更改它, AKA 相同名称和相同类型的结果和方法参数 - 参数名称应该无关紧要)。

      【讨论】:

        【解决方案7】:

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

        所以如果你在 C1 方法中有 int 类型,那么你需要在 C2 方法中也有 int 类型,然后 JVM 会按照你的意愿从 C2 运行方法。

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

        【讨论】:

          【解决方案8】:

          通过查看您的代码,您并没有利用继承来获得您想要的答案。你必须改变这一行

          C1 c = new C2();
          

          C2 c = new C2();
          

          【讨论】:

            【解决方案9】:

            由于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) 不同的方法。

            【讨论】:

              【解决方案10】:

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

              由于 double 优先于 int,因此调用了具有双参数的方法。这是因为 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
              

              【讨论】:

                猜你喜欢
                • 2011-10-17
                • 2018-01-05
                • 1970-01-01
                • 2013-04-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-10-01
                • 1970-01-01
                相关资源
                最近更新 更多