我有一个一般情况的代码:
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)
。
还必须满足多态性的要求: 选择的方法应包含在父类定义中。
有人可以详细说明显示的结果吗?
答案 0 :(得分:2)
为了弄清楚为什么两次获得结果B and A
,您需要知道有两部分:编译和运行时。
编译
遇到语句a.show(b)
时,编译器将执行以下基本步骤:
a
)并获取其声明的类型。此类型为A
。A
及其所有超类型中,列出所有名为show
的方法。编译器将仅找到show(A)
。它不会查看B
或C
中的任何方法。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
时,还需要执行一些步骤。
a
。a = new B()
起,此类型为B
。B
,然后尝试找到方法show(A)
。由于B
覆盖了此方法,因此找到了该方法。如果不是这种情况,它将在超类(A
和Object
)中查找,直到找到这样的方法为止。重要的是要注意,它仅考虑show(A)
方法,例如从未考虑过show(B)
中的B
。show(A)
调用方法B
,从而得到String
B and A
。有关更多详细信息,请参见spec for invokevirtual
:
如果解析的方法不是签名多态的(第2.9节),那么invokevirtual指令将按以下步骤进行。
让C成为objectref的类。通过以下查找过程选择要调用的实际方法:
如果C包含一个实例方法m的声明,该声明将覆盖已解析的方法(第5.4.5节),则m是要调用的方法,并且查找过程终止。
否则,如果C具有超类,则使用C的直接超类递归地执行相同的查找过程;要调用的方法是此查找过程的递归调用的结果。
否则,将引发AbstractMethodError。
例如,objectref
是a
,其类是B
,解析的方法是{{1}中的方法(invokevirtual
中的方法{1}})
tl:dr-编译时确定要调用的方法,运行时确定从何处调用它。
答案 1 :(得分:0)
我认为您的问题与另一个主题有关-区分对象 和参考。从认证的SE 8专业程序员II中: 在Java中,所有对象都是通过引用访问的,因此,作为开发人员,您永远无法直接访问 到对象本身的内存中。从概念上讲,您应该考虑对象 作为内存中存在的实体,由Java运行时环境分配。而不管 对象在内存中具有的引用类型,即对象本身 不变例如,由于所有对象都继承java.lang.Object,因此它们都可以是 重新分配给java.lang.Object,如以下示例所示:
Lemur lemur = new Lemur();
Object lemurAsObject = lemur;
即使为狐猴对象分配了其他类型的引用, 该对象本身并没有改变,仍然作为Lemur对象存在于内存中。什么 那么,已经改变了我们使用以下方法访问Lemur类中的方法的能力: lemurAsObject参考。在没有明确转换为狐猴的情况下,您将在下一个中看到 部分,我们将无法再访问该对象的Lemur属性。
我们可以用以下两个规则来总结这一原理:
答案 2 :(得分:0)
在您的示例A a = new B()
中,a
是多态引用-可以指向类层次结构中不同对象的引用(在这种情况下,它是对object的引用类型为B
的对象,但也可用作对类A
的对象的引用,该类在对象层次结构中排在最前)。
关于您要询问的特定行为:
B
?将通过引用变量调用哪个特定的show(B obj)
方法,取决于在特定时间点对其持有的对象的引用。也就是说:如果它持有对类B
的对象的引用,则将调用该类中的方法(在您的情况下),但如果它指向类A
的对象,则该对象的引用该对象将被调用。这就解释了为什么在输出中打印B
的原因。
层次结构)。
and A
?方法称为方法重载。它使用静态绑定,这意味着适当的方法将在编译时绑定。编译器不知道对象的运行时类型。
因此,在这种情况下,show(A obj)
类的A
将受到约束。但是,当在运行时中实际调用该方法时,将调用类B
中的实现(show(A obj)
中类B
的实现),因此,您请参见输出中的B and A
而不是A and A
。
Reference for invokevirutal
(an JVM instruction called when virtual methods are executed):
如果解析的方法不是签名多态的(第2.9节),则 invokevirtual指令的执行过程如下。
让C成为objectref的类。实际要调用的方法是 通过以下查找过程选择:
如果C包含重写的实例方法m的声明 (第5.4.5节)解析的方法,则m是要调用的方法,并且 查找过程终止。
否则,如果C具有超类,则相同的查找过程为 使用C的直接超类递归执行;该方法 被调用是此查找的递归调用的结果 程序。
否则,将引发AbstractMethodError。
对于a.show(c)
,适用于B
的相同规则,因为C
不会重载任何方法,而直接从B
继承。
编辑:
逐步解释a.show(c)
为什么打印B and A
的原因:
a
识别为类objectref
的对象A
(编译时)a
的类型为A
,所以方法A::show(A obj)
被绑定。show()
上调用a
方法),运行时将识别出引用a
多态指向类型为B
的对象(即由于A a = new B()
)(运行时)C extends B
,运行时将a.show(c)
视为b.show(c)
(或b.show(b)
),因此在这种情况下使用B::show(A obj)
代替obj
使用类型B
的对象。这就是为什么要打印“ B and A”的原因。答案 3 :(得分:0)
您的引用类型为A
,而A
仅具有一种方法show(A obj)
,该方法已在B
中被覆盖并打印B and A
,这就是为什么总是打印B and A
。