为什么调用了错误的方法:奇怪的继承行为

时间:2014-10-06 16:39:39

标签: java inheritance method-overriding

所以这个问题是关于继承和方法覆盖。 具体来说:子类具有与父类相同的名称方法但具有不同签名的情况,例如:

class A has methodX(String arg)
class B extends A has methodX(int arg)

在正常情况下,将根据参数调用正确的方法。

但是在下面的代码中我遇到了一些我无法解释的奇怪行为:

static class A {
    public void method1() {
        System.out.println("m1.A");
    }
    public void method4(A arg) {        //Original method4
        System.out.println("m4.A");
    }
}

static class B extends A {
    public void method1() {
        System.out.println("m1.B");
    }
}

static class C extends B {
    public void method4(A arg) {        //Override method4 from Class A
        System.out.println("m4.C");
    }
}

static class E extends C {
    public void method1() {
        System.out.println("m1.E");
    }
    public void method4(E arg) {        //NO OVERRIDE: Same name, but different method4 than in class A or C
        System.out.println("m4.E");
    }
}


public static void main(String[] args) {
    A va = new A();
    B vb = new B();
    C vc = new C();
    E ve = new E();

    //At this point everything is fine
    ve.method4(ve);  //Calls method4 from class E based on parameter type - CORRECT
    ve.method4(va);  //Calls method4 from class C based on parameter type - CORRECT

    //After this code strange things happen
    vc = new E();
    vb = vc;

    vb.method1();    //Output: m1.E; method1 from class E is called - CORRECT
    vb.method4(vb);  //Output: m4.C; method4 from class C is called - why?

    vc.method1();    //Output: m1.E; method1 from class E is called - CORRECT
    vc.method4(vc);  //Output: m4.C; method4 from class C is called - why?
    vc.method4(ve);  //Output: m4.C; method4 from class C is called - why?

}

所以上面的程序输出是:

m4.E
m4.C

m1.E
m4.C  //why? Expected: m4.E

m1.E
m4.C  //why? Expected: m4.E
m4.C  //why? Expected: m4.E

的行为
vb and vc

是我无法理解的。有什么想法吗?

3 个答案:

答案 0 :(得分:3)

我猜你打算期待 m4.E。但是不要忘记重载解析是在编译时执行的,而不是在执行时执行。

CB都没有可用的method4(E)方法,因此编译器会解析对method4(A)方法的调用... 不是< / em>被E覆盖。您在问题中m4.C // why?拨打的所有电话都会调用带有签名method4(A)的方法,并在E的实例上调用。现在E没有覆盖method4(A),所以它留在C中的实现,它会打印m4.C

这里没什么奇怪的。

答案 1 :(得分:3)

您没有在课程method4和课程C之间覆盖E:他们有不同的签名。

使用method1时,它不带参数,因此子类将覆盖现有方法。但编译器看到了

public void method4(E arg);

public void method4(C arg);

完全不同(就像他们有不同的名字一样)。

您应该尝试使用@Override在子类中注释您的方法。您会发现它会在method1上允许它并在method4上投诉。

对于最后几行,编译器将

  1. 在课程method4中查找C,因为vc的声明类型为C
  2. 找到适合的method4,看看它是A的参数。
  3. 在运行时,JVM将

    1. 查看编译器选择的方法是否在实际类型vc的子类中被覆盖。
    2. 发现此方法未被覆盖,因此请使用C
    3. 中的方法

      我认为混乱来自于这三条线紧密相连:

      C vc = new C();
      // ...
      vc = new E();
      // ...
      vc.method4(vc);  //Output: m4.C; method4 from class C is called - why?
      

      您和我可以看到vc的实际类型为E,因为您刚刚创建了new E()并将其分配给vc。但是通常不能期望编译器进行这种推断:在实际应用程序中,代码可能要复杂得多,而且在编译时没有任何确定性可以解析为实际类型的代码。中间线可能是

      vc = runningOnAThursday ? new E() : new C();
      

      所有编译器都可以查看声明的类型,即C。只有在运行时才能检查实际的类型(因此,有关重写的方法)。

答案 2 :(得分:1)

你让我失去了5个类之间的4种方法,但我可以猜测你期望调用实例的实际类型的方法。

但是,这不是一个有效的假设,因为Java只能使用Object的最具体的已知类型来调用该方法。

这是一个更简单的例子,演示了我在说什么:

public class Test {

    public static void method(String obj){
        System.out.println("String");
    }

    public static void method(Object obj){
        System.out.println("Object");
    }


    public static void main(String... args){
        Object string = "testing";
        method(string);
    }
}

请注意,此代码调用方法(Object)函数,即使字符串变量的实际类型是String。这是因为Java知道字符串变量的最具体的类型是Object,而不是String。