【问题标题】:Inheritance and overloading in JavaJava中的继承和重载
【发布时间】:2015-12-25 14:32:31
【问题描述】:

我正在准备考试,我需要一些帮助来理解以下代码片段中发生了什么。

class A {
    public void method1(A X) {
        System.out.println("A");
    }
}
class B extends A {
    public void method2() {
        System.out.println("B");
    }
}
class C extends B {
    public void method1(A x) {
        System.out.println("C");
    }
}
class D extends C {
    public void method1(D x) {
        System.out.println("D");
    }
}
public class test {
    public static void main(String[] args) {
        C c = new D();
        B b = c;
        c.method1(c);  // prints C
        b.method1(b);  // prints C
    }
}

好吧,这就是我的想法:c.method1(c) 调用 C 中的 method1 而不是 D 中的 method1,因为 c 被清除为 C,因此 D 中的 method1 不可见。但是 b.method1(b) 更难。 B 没有method1,我假设将使用超类中的method1,但事实并非如此。为什么它使用C中的方法?我在 b 中放了一个新的 D,但 D 的专业化没有任何可见性,因为 b 来自 B 类型。

【问题讨论】:

  • 今日谜题 ;)
  • 两个语句作为答案的补充:1) 重载在 编译时 完全解决,而重写方法的方法解析是 运行时行为 . 2) 要覆盖方法,您必须使用相同的方法签名。请记住,返回类型不是签名的一部分(至少在 Java 中)。

标签: java inheritance methods invoke


【解决方案1】:

如果我猜对了,你的问题是,为什么你打印了“C”,而不是像你预期的那样为b.method1(b) 打印了“A”。 答案很简单,您在这里处理的是动态调度(动态绑定)和静态调度(静态绑定)的组合。第一个是根据运行时类型完成的,第二个是根据编译时类型完成的。

注意,顺便说一句,在 D 中您有 两个 方法,具有不同的签名(请记住,要覆盖,您需要完全匹配的签名,所以在这种情况下它是重载 == compile-时间多态性):

public void method1(D x)
public void method1(A x)

那么,当你执行b.method1(b) 时,这里会发生什么。

您需要首先确定要调用的方法签名,然后找到匹配的方法。第一个是基于编译时类型。您传递的参数属于 B 类型(隐式向上转换),因此它选择 method1(A x)method1(D x) 的签名不适合)并将在继承树上向上搜索 method1(A x) 以找到匹配项。第一场比赛在 C 中。 更多信息请看这里:JLS,尤其是。解释:

当调用方法时(第 15.12 节),实际参数的数量(以及 任何显式类型参数)和 在编译时使用参数来确定 将被调用的方法 (§15.12.2)

【讨论】:

  • 如果 D 中的 method1(D x) 是 method1(A x),jvm 会考虑它的调用吗?我认为不是因为 b 被声明为 B ,因此该方法在编译时不可见,所以它不能在运行时使用,还是我磨损了?
  • 是的,它是可见的,因为它是覆盖而不是重载。在那种情况下,你得到了输出“D”。同样,不要混淆静态多态性,即根据签名选择方法,以及从继承层次结构中选择最佳匹配的继承。一旦找出正确的签名,b.method1 调用就会在运行时解决。并且由于在运行时 b 实际上是 D 类型,因此还将查找来自 D 的重写方法。
  • 我鼓励你尝试用你能想象到的所有修改来运行它,它可能很容易证明或反驳你的假设 ;)
  • 谢谢!但是静态多态在这里也很重要。如果我被写成,D c = new D();然后它会打印“D”。如果编译器可以静态解决调用目的地,JVM会考虑动态调度功能吗?
【解决方案2】:

当您调用对象的方法时,主体很容易。系统会从对象的class of instantiation开始研究该方法,然后他会去找它的直接母亲,直到找到它。例如 : 如果您使用D class 进行实例化,并且假设method1 仅存在于A class 中。 所以系统会配合这个步骤的研究:

D(not found) > C(not found) > B(not found) > A(found) 

所以他会使用A class method

【讨论】:

    【解决方案3】:

    让我们来看看你的示例代码并注释调用了哪个方法、为什么调用它以及为什么这里的所有东西都不是继承的。

    class A {
        public void method1(A X) {
            System.out.println("A");      <--|
        }                                    |
    }                                        |
    class B extends A {                      |
        public void method2() {              | This is the only overriding happening
            System.out.println("B");         | because they share the signature method1(A)
        }                                    | The other signatures are method1() from class B
    }                                        | and method1(D) from class D
    class C extends B {                      |
        public void method1(A x) {           |
            System.out.println("C");         |
        }                             -------
    }
    class D extends C {
        public void method1(D x) {
            System.out.println("D");
        }
    }
    public class test {
        public static void main(String[] args) {
            C c = new D();
            B b = c;
            c.method1(c);  // prints C
            b.method1(b);  // prints C
        }
    }   
    

    由于您从定义为 C 的 Object 中调用 method1,因此您将得到结果 C。 尽管有Object 类的方法,但该类已知的唯一方法是method1(A) 和method1()。由于你的班级 被定义为 C 的它会正确调用类 C 中的方法,因为 method1 函数 来自D 的类不会覆盖C 的类。

    编辑以回答您的评论:

    此时您正在调用带有签名method1(A) 的方法。由于您的Object 实际上代表了D 类,它会注意到方法method1(A) 被先前继承的类C 覆盖,因此将打印C。简单来说,类的定义定义了允许调用的方法(在对象的可见性范围内),但它引用的对象的实际类型定义了方法的调用方式(如果它被覆盖)在某些时候)。

    【讨论】:

    • 谢谢,你说得更清楚了。但是为什么 b.method(b) 也打印“C”? b 是表单类型 B,所以它只能在 B 中看到 method2,在 A 中看到 method(A X)。我认为它应该在 A 中调用 method1?
    • 感谢您的编辑。我可以用我自己的话来看看我是否理解正确。 b 的唯一可见方法是 method1(A x) form A 和 method2() form B。如果我调用 b.method1(b),JVM 会查看实际对象 b(左侧)并搜索最特殊的实例方法1。所以它看到方法1(A X)形式A和形式C。两者都适合,因为b a也是A,但它采用方法1(A X)形式C,因为它更专业。 method1(D x) 形式 D 不考虑调用,因为它不会覆盖,因此对 b 不可见。
    【解决方案4】:

    总之,这里是每个方法在每个继承级别的可见性:

    D 类:

    public void method1(D x) { System.out.println("D"); }
    public void method1(A x) { System.out.println("C"); }
    public void method2()    { System.out.println("B"); }
    

    C 类:

    public void method1(A x) { System.out.println("C"); }
    public void method2()    { System.out.println("B"); }
    

    B 类:

    public void method1(A x) { System.out.println("A"); }
    public void method2()    { System.out.println("B"); }
    

    A 类:

    public void method1(A x) { System.out.println("A"); }
    

    同样重要的是要指出,您的示例中的两个变量“c”和“b”都是同一个对象,并且是 D 的一个实例。

    所以...如果你调用c.method1(c);,它会打印“C”,因为D是A的一个实例(它实际上是一个D,但它也是一个继承的A),所以你可以调用method1(A),它对于 D,打印“C”。 (那是满嘴巴)。你会问为什么它不打印“D”?因为变量声明为C,编译器只能链接到method1(A)

    如果您调用b.method1(b);,它会打印“C”,因为您的变量b 实际上是D 的一个实例,因为您将它创建为new D()。实际上cb 指向同一个D 类型的对象。

    当调用一个方法时,JVM 会查看对象 D 的实际类型,而不是它声明为 B 的类型。

    记住的好方法是当你有类似的东西时

    B b = new D()

    等式的左边部分主要由编译器使用。请记住,method1(A)method1(D) 是两种不同的方法(因为不完全相同的签名,不同的参数类型)。

    等式的右边部分由 JVM 在运行时使用。它在运行时定义了该变量 b 的实际类型。

    【讨论】:

      【解决方案5】:

      如果我们看到 class C 中的纯代码方法 method1 被 B 覆盖,而 B 最终继承自 class A。由于覆盖是运行时多态性,尽管您创建了 D 类的实例,但由于这两种方法不同,因此调用 C 类method1

      如果你想调用D的方法,你需要将方法的参数更改为A

      【讨论】:

        【解决方案6】:

        您的方法覆盖已损坏。

        C 类中的方法等价于D 类中的方法是不同的。你不是正确的ovverdien。签名不一样。

        你可以在你的方法上面写@Override来测试它。

            @Override
            public void method1(D x) {
                System.out.println("D");
            }
        

        由于您在子类中更改了该方法的签名,因此上述代码无法编译并给您一个错误。

        【讨论】:

          【解决方案7】:

          引用实例的变量类型与调用的方法无关。

          object的类型定义了调用哪个方法;子类覆盖方法以改变实例的行为,同时仍然保持它们对超类类型变量的可分配性。这就是 OO 的精髓——见Liskov substitution principle

          然而,在重载方法时,持有参数的变量的类型控制着调用哪个方法——它所引用的实例的类型是无关紧要的。

          【讨论】:

          • 好的,但为什么 c.method1 调用 C 中的方法而不是 D 中的方法?
          • @Asker 因为D.method1(D) 确实 覆盖方法C.method1(A),而是重载它。
          猜你喜欢
          • 1970-01-01
          • 2012-06-22
          • 1970-01-01
          • 2014-05-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-07
          • 2011-02-27
          相关资源
          最近更新 更多