【问题标题】:How does a polymorphic instance select its methods?多态实例如何选择它的方法?
【发布时间】:2019-01-21 20:21:51
【问题描述】:

我无法理解这段代码的行为。 a被定义为A,c被定义为C。 然后,在公共课结束时,a = c。 当 a 调用 display() 方法时,它会到达它的 C 版本。 但是当 a 调用 f() 时,它只能到达 A 版本,尽管第一个参数(字节和 long)比 float 更符合 long。

这是一本书的练习,但解释很少,或者根本不存在。

class A{
    public void display(){
        System.out.println("I am an A ");
    }

    public void f(double x){
        System.out.println("A.f(double = " + x + ") ");
    }
}

class C extends A{
    public void display(){
        System.out.println("I am a C ");}

    public void f(long q){
        System.out.println("C.f(long = " + q + ") ");}
    }


public class PolySurStack{
    public static void main(String Args[]){
        byte bb =1; long q = 4; float x = 5.f;

        System.out.println(" ** A **");
        A a = new A(); a.display();
        a.f(bb); a.f(x);

        System.out.println();
        System.out.println(" ** C **");
        C c = new C(); c.display();
        c.f(bb); c.f(q); c.f(x);
        System.out.println();
        a = c; a.display();
        a.f(bb); a.f(q); a.f(x);
    }
} 

【问题讨论】:

  • 您可以通过删除所有不必要的代码以使其最小化来提供一个更好的示例(请参阅minimal reproducible example),但这仍然是一个有趣的问题。我不知道答案,但恐怕这是一个重复的问题。

标签: java polymorphism overriding overloading


【解决方案1】:

当您调用a.f(bb)a.f(q)a.f(x) 时,编译器只能选择在类A(或A 的任何超类)中定义的方法签名,因为aA 类型的引用变量。

因此,只考虑public void f(double x)。为了使public void f(long q) 成为重载解决方案的候选对象,您必须在调用f() 之前将a 转换为类型C,因为只有C 类定义了具有该签名的方法。

要理解的重要一点是方法重载决议发生在编译时。只有您为其调用方法的引用变量的编译时类型决定了哪些方法签名是方法重载决议的候选者,以及将选择哪个候选者。

【讨论】:

  • 也就是说,发生这种情况是因为display() 在编译时是可确定的,而f() 不是?
  • @Gendarme display()f(double x) 在编译期间都是可确定的(当方法调用是针对 A 类型的变量时)。然而,在display() 的情况下,方法覆盖在运行时启动 - 因为C 有一个display 方法具有相同的签名,如果a 持有对类@ 实例的引用987654341@,Cdisplay方法被执行而不是A的。
  • 好吧,Eran,这是我的误解,如果,正如你所说:“编译器可以选择的唯一方法签名是在 A 类(或 A 的任何超类)中定义的那些”,为什么会这样?在 C 中调用显示?
  • 因为C 覆盖了display() 而它只是重载 f(), @Tchae。
  • @Gendarme,那么 a 只能调用 A 中的方法不是真的吗?例如,如果它引用一个 C 对象,它可以访问 C 中的方法?
【解决方案2】:

我刚刚在另一个论坛上发现了这个:

重载:(函数名相同但签名不同)

  1. 在同一个类中具有相同名称但参数不同的两个或多个方法称为重载。

  2. 当您想要扩展功能时使用重载。

  3. 重载称为编译时多态性

覆盖:(相同的函数名称但相同的签名)

  1. 两个或多个方法在父类和子类中具有相同的方法名称和相同的参数,称为覆盖。

  2. 当您想要重用现有功能时使用覆盖。

  3. 覆盖称为运行时多态

所以我的问题的答案似乎是覆盖分辨率(如 display() )发生在运行时(在 a = c 之后),而重载分辨率(如 f() )发生在编译时,当 a仍然是 A。

我认为。

我还发现了这个页面:https://beginnersbook.com/2013/04/runtime-compile-time-polymorphism/

要清楚并与该主题高度相关。

【讨论】:

    【解决方案3】:

    编译器选择的方法取决于声明的类型,而不是运行时类型。
    第一个声明为变量A 的系列只能调用A 方法(无论实例化为A 的运行时类型仅派生自Object):

    A a = new A(); 
    a.f(bb); a.f(x);
    

    对于第二个系列,由于CA,因此编译器将具有最具体参数匹配的方法绑定到调用,因此编译器可以在此处绑定这些方法的任何公共方法:

    C c = new C(); 
    c.f(bb); c.f(q); c.f(x);   
    

    但在最后一段可能质疑自己的代码中,aC 称为运行时对象,但将 A 称为声明类型:

    A a = new A(); 
    // ...
    a = c; 
    a.f(bb); a.f(q); a.f(x);
    

    因此只能调用A 中定义的方法。

    【讨论】:

    • 但是为什么在“最后一个块”中 a.display() (你丢弃的)调用了 C 中的显示方法(即“我是 C”)?
    【解决方案4】:

    我将尝试澄清@eran 的答案,以便您理解它。

    一个子类拥有它的超类的所有方法,可能还有更多。您有一个A 类型的变量,其中存储了C 类型的对象。 C 具有类A 中定义的所有方法,还有一个附加方法f(long q)A 不知道这个新方法,所以由于你将对象存储在A 的变量中,所以你不能调用f(long q)

    可以调用display(),因为它是在A 中定义的,但执行它的仍然是C 对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-19
      • 1970-01-01
      • 2021-10-02
      • 2018-09-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多