繁体   English   中英

Java中的多态如何适用于这种一般情况(带参数的方法)?

[英]How does polymorphism in Java work for this general case (method with parameter)?

我有一般情况的代码:

public class A {
    public String show(A obj) {
        return ("A and A");
    }
}

public class B extends A {
    public String show(B obj) {
        return ("B and B");
    }

    public String show(A obj) {
        return ("B and A");
    }
}

public class C extends B {

}

public class Test {
    public static void main(String[] args) {
        A a = new B();
        B b = new B();
        C c = new C();

        System.out.println("1--" + a.show(b));
        System.out.println("2--" + a.show(c));     
    }
}

结果是:

1--B and A
2--B and A

我知道Java中存在从高到低的优先级链:

this.show(O), super.show(O), this.show((super)O), super.show((super)O)

我的理解如下:

在这段代码中:

A a = new B()

发生了一场骚乱。 A是父类引用,B是子父类引用。 编译并运行代码时,子父类引用确定如何选择方法。 在这种情况下,选择B类中的show(A)

还需要多态性必须满足:所选方法应包含在父类定义中。

有人可以对显示的结果做出更详细的解释吗?

为了得到结果B and A两次的原因,你需要知道这有两个部分:编译和运行时。

汇编

遇到语句a.show(b) ,编译器会采取以下基本步骤:

  1. 查看在( a )上调用该方法的对象并获取其声明的类型。 这种类型是A
  2. A类及其所有超类型中,列出名为show的所有方法。 编译器只会找到show(A) 它没有查看BC中的任何方法。
  3. 从找到的方法列表中,选择与参数( b )最匹配的方法(如果有)。 show(A)将接受b ,因此选择此方法。

传递c的第二个调用也会发生同样的事情。 前两个步骤是相同的​​,第三步将再次找到show(A)因为只有一个,它也匹配参数c 因此,对于您的两个调用,其余过程都是相同的。

一旦编译器弄清楚它需要什么方法,它将创建一个字节码指令invokevirtual ,并将解析后的方法show(A)作为它应该调用的方法(如Eclipse中所示,打开.class ):

invokevirtual org.example.A.show(org.example.A) : java.lang.String [35]

运行

运行时,当它最终到达invokevirtual时,还需要执行几个步骤。

  1. 获取调用该方法的对象(此时已经在堆栈中),这是a
  2. 查看此对象的实际运行时类型。 由于a = new B() ,此类型为B
  3. 查看B并尝试找到方法show(A) 由于B覆盖它,因此找到此方法。 如果不是这种情况,它会查找超类( AObject ),直到找到这样的方法。 重要的是要注意它只考虑show(A)方法,例如。 从未考虑过来自B show(B)
  4. 运行时现在将从B调用方法show(A) ,给出String B and A作为结果。

有关这方面的更多细节在invokevirtual规范中给出:

如果已解析的方法不是签名多态(第2.9节),则invokevirtual指令如下进行。

设C为objectref的类。 要调用的实际方法由以下查找过程选择:

如果C包含一个覆盖(§5.4.5)已解析方法的实例方法m的声明,则m是要调用的方法,并且查找过程终止。

否则,如果C具有超类,则使用C的直接超类递归地执行相同的查找过程; 要调用的方法是递归调用此查找过程的结果。

否则,引发AbstractMethodError。

对于您的示例, objectrefa ,其类是B并且已解析的方法是来自invokevirtualshow(A)来自A


tl:dr - 编译时确定要调用的方法,运行时确定从哪里调用它。

我认为你的问题涉及另一个主题 - 区分对象和参考。 来自Certified Professional SE 8 Programmer II:在Java中,所有对象都通过引用访问,因此作为开发人员,您永远无法直接访问对象本身的内存。 但是,从概念上讲,您应该将对象视为存储在内存中的实体,由Java运行时环境分配。 无论您在内存中对象的引用类型如何,对象本身都不会更改。 例如,由于所有对象都继承java.lang.Object,因此可以将它们全部重新分配给java.lang.Object,如以下示例所示:

Lemur lemur = new Lemur();
Object lemurAsObject = lemur;

尽管已经为Lemur对象分配了具有不同类型的引用,但对象本身并未更改,并且仍然作为内存中的Lemur对象存在。 那么,改变的是我们使用lemurAsObject引用访问Lemur类中的方法的能力。 如果没有明确的回归Lemur,正如您将在下一节中看到的那样,我们无法再访问对象的Lemur属性。

我们可以用以下两个规则来总结这个原则:

  1. 对象的类型确定内存中对象中存在哪些属性。
  2. 对象引用的类型确定Java程序可以访问哪些方法和变量。

在您的示例A a = new B()a多态引用 - 可以将不同对象指向类层次结构的引用(在这种情况下,它是对类型B对象的引用,但也可以用作对类A对象,它是对象层次结构中的最顶层)。

至于你要问的具体行为:

为什么B在输出中打印?

将通过引用变量调用哪个特定show(B obj)方法取决于它在某个时间点保持的对象的引用。 也就是说:如果它持有对B类对象的引用,那么将调用该类的方法(这是你的情况),但如果它指向类A的对象,则会调用该对象的引用。 这解释了为什么B在输出中打印。

层次)。

为什么and A打印在输出中?

具有相同名称但签名不同的子类中的方法称为方法重载 它使用静态绑定,这意味着在编译时绑定适当的方法。 编译器不知道对象的运行时类型。

因此在这种情况下, A类的 show(A obj)将受到约束。 然而,当该方法将在运行时实际上是调用,从类它的实现B将被调用( show(A obj)从类B ),这就是为什么你看到B and A ,而不是A and A输出。


invokevirutal参考(执行虚方法时调用的JVM指令)

如果已解析的方法不是签名多态(第2.9节),则invokevirtual指令如下进行。

设C为objectref的类。 要调用的实际方法由以下查找过程选择:

如果C包含一个覆盖(§5.4.5)已解析方法的实例方法m的声明,则m是要调用的方法,并且查找过程终止。

否则,如果C具有超类,则使用C的直接超类递归地执行相同的查找过程; 要调用的方法是递归调用此查找过程的结果。

否则,引发AbstractMethodError。


对于a.show(c) ,适用与B相同的规则,因为C没有重载任何方法,而是直接从B继承。

编辑:

逐步解释为什么a.show(c)打印B and A

  1. 编译器将对象a识别为对类A对象的objectref (编译时)
  2. 因为aA类型,所以绑定方法A::show(A obj)
  3. 当代码实际执行时(即在对象a )上调用show()方法,运行时会识别a多态指向B类对象的引用(因为A a = new B() )(运行时)
  4. 因为C extends B ,运行时会像处理b.show(c) (或b.show(b) a.show(c)一样处理b.show(c) ),所以在这种情况下使用B::show(A obj)但是到位的obj类型的对象B被使用。 这就是打印“B和A”的原因。

您的参考类型是AA只有一个方法show(A obj)已在B重写并打印B and A ,这就是为什么你总是得到B and A打印。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM