对于以下代码,在插入参数" A2 a"和" B2 b"在A2类和B2类中,输出已经改变。我想知道为什么?
A1班级:
public class A2
{
public void m1(A2 a)
{
System.out.println("A2");
}
}
B2班级:
public class B2 extends A2
{
public void m1(B2 b)
{
System.out.println("B2");
}
}
主类(运行程序):
public class D2
{
public static void main(String[] args)
{
A2 x = new A2();
A2 y = new B2();
B2 z = new B2();
y.m1();
}
}
答案 0 :(得分:1)
在插入参数之前,B2.m1()
覆盖A2.m1()
,因此调用的方法仅依赖于接收器的运行时类型。
插入参数后,两个m1
方法具有不同的类型签名,B2.m1(B2 b)
不再覆盖A2.m1(A2 a)
。这样调用的方法取决于接收者和发送参数的编译时类型。
假设:
A2 x = new A2();
A2 y = new B2();
B2 z = new B2();
y.m1(x); // calls A2.m1(A2 a)
y.m1(z); // calls A2.m1(A2 a)
z.m1(x); // calls A2.m1(A2 a)
z.m1(z); // calls B2.m1(B2 b)
在添加参数之前:
y.m1(); // would have called B2.m1()
这是你看到的差异吗?
编辑:下面添加了很多详细信息。
首先,您可以将签名视为与数字结合的方法的名称
和形式参数的类型。您可能会看到它们以“m1:(LA2;)V”或“m1:(LB2;)V”的形式写入
(名为“m1”的方法,它接受类类型A2
的1个参数并返回Void)。但我会在这里
写m1(A2)
或m1(B2)
以使其更容易。
确定最终调用的方法基本上有两个步骤。编译时步骤 和一个运行时步骤。编译时步骤确定要调用的方法的签名。该 运行时步骤通过查找具有该签名的那个类来选择要调用的实际方法 对象的创建时间。如果它没有在那里找到它,它会向上移动类层次直到a 找到带有该签名的方法(必须有一个或者它不会被编译)。
要查找签名,使用编译时类型(也称为“静态类型”)(那些 用于声明变量)。这适用于接收器和参数。
那么让我们看看在每种情况下选择的签名:
y.m1(x)
y被声明为A2
,因此唯一可能的签名是m1(A2)
。
y.m1(z)
同样,y
是A2
,因此唯一的可能性是m1(A2)
。 z
是B2
的事实很好
您可以随时传递B2
,预计会出现A2
。 (在此过程中,事实是这样的
在运行时y
将创建为B2
无关!)
z.m1(x)
z
是B2
。在B2
中有两种可能性。 m1(B2)
和继承的m1(A2)
。自x
以来
是A2
,第一个是不可能的(您无法通过A2
预期B2
,所以一次
我们再次留下m1(A2)
。
z.m1(z)
z
是B2
。在B2
中有两种可能性。 m1(B2)
和继承的m1(A2)
。自z
以来
是B2
,两者都是可能的。在这里,Java有一个规则来选择哪个是带有的
最具体(最派生)的参数类型,因此选择m1(B2)
。
在上述所有情况中,没有什么可以在运行时决定,因为在每种情况下都有
只有一个具有给定签名的方法。以y.m1(z)
为例。在运行时,Java会寻找
在编译时步骤中确定的带有签名m1(A2)
的方法。
首先,它会在B2
类中查找,因为y
是作为B2
对象创建的。但这里没有方法
有那个签名(只有m1(B2)
)。一旦z
成为B2
对象,这并不重要
签名是在编译时步骤中确定的,只会调用具有该签名的方法
在运行时。因此,Java上升到层次结构并在类中找到具有正确签名的方法
A2
,因此结果就是你所看到的。 (请注意,这是概念性的和实际的
为了使它成为正确的方法而生成的代码比那更聪明。)
现在,在添加参数之前,两个m1方法都被命名为“m1”并且没有参数,因此它们具有
相同的签名m1()
。所以看着
y.m1()
编译时步骤:y
是A2
,因此唯一可能选择的是m1()。
然后在运行时,由于y
被创建为B2
,Java在类B2
中开始寻找方法
有那个签名。它找到一个! m1()
中的B2
方法与m1()
具有相同的签名
A2
中的方法。换句话说,与上述情况不同,B2
。m1真正覆盖A2.m1
。所以java是
很高兴在m1()
中调用B2
的版本,正如您在添加参数之前所看到的那样。
这就是故事。我知道这很令人困惑,但它对理解Java(和其他对象)非常关键 你正在深入研究这种语言而且非常好。
阅读有关覆盖与重载,多态,动态调度和虚拟的所有内容 方法表。
我认为最重要的一点是:
- 只有覆盖方法时才会根据运行时进行动态调度 接收器的类型
- 派生类中的方法仅在签名时覆盖基类中的方法 是一样的
答案 1 :(得分:0)
y.m1()
现在需要将参数传递给方法。这段代码甚至可以编译吗?由于您已将y构造为B2对象,因此调用y.m1()
将运行B2对象中的代码而不是A2对象。
https://docs.oracle.com/javase/tutorial/java/IandI/polymorphism.html