标题可能会产生误导,但作为非本地人,我无法找到更好的标题。
假设我有两个课程,Dog
和Fox
:
public class Dog {
public String bark() {
return "Wuff";
}
public String play(Dog d) {
return "Wuff" + d.bark();
}
}
public class Fox extends Dog {
public String bark() {
return "Ringding" ;
}
public String play(Fox f) {
return "Ringding" + f.bark();
}
}
我创建了一些实例,并调用了一些方法
Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(hybrid.play(foxi)); // Output number 1
System.out.println(foxi.play(hybrid)); // Output number 2
对于1.输出我预期"RingdingRingding"
因为hybrid
实际上是对Dog
实例的引用,即使引用的类型为Dog
它仍然引用一个Fox
对象,但我仍然得到了这个输出:
WuffRingding
第二个我遇到了同样的问题,因为foxi
是Fox
的实例而hybrid
实际上是Fox
的一个实例(无论参考什么,对吧?),输出应该是"RingdingRingding"
但是又一次,我得到了:
WuffRingding
有人可以解释原因吗?
答案 0 :(得分:2)
方法调用的两个重要事项。
你有两次:编译时间和运行时间 这两次之间的规则并不相同。
在编译时,编译器必须静态地确定调用哪个方法的精确签名才能编译好。
此绑定是静态的,因为编译器与调用该方法的具体实例无关,并且对于传递给该方法的参数也是如此。
编译器不依赖于有效类型,因为在运行时有效类型可能会在执行流程中发生变化
因此编译器在可用方法中搜索声明的类型,这是根据传递给它的声明的参数类型更具体的方法。
在运行时,将根据调用该方法的有效实例使用来自类或其他实例的实例方法。
但是调用的方法必须遵守编译时指定的签名。
1)对于第一种情况:
Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(hybrid.play(foxi)); // Output number 1
对于Dog实例,编译器必须找到最具体的方法play()
,并将参数作为Fox声明类型的变量。
在Dog类中,存在具有兼容签名的单个方法play()
:
public String play(Dog d) {
因此,此签名用于绑定:String play(Dog d)
。
关于bark()
方法,很明显因为只有一个bark()方法签名。
因此,我们对编译时绑定的方法没有歧义
在运行时,调用具体实例的String play(Dog d)
方法。
hybrid
变量引用Fox的实例,但Fox不会覆盖
String play(Dog d)
。 Fox定义了一个play()方法,但使用了另一个签名:
public String play(Fox f) {
因此JVM调用Dog的public String play(Dog d) {
方法
然后,当执行d
并d.bark()
引用d
实例时,它会调用有效类型Fox
的方法。
输出“WuffRingding”。
2)对于第二种情况:
Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(foxi.play(hybrid)); // Output number 2
对于Fox
实例,编译器必须找到最具体的方法play()
,其参数为Dog
声明类型的变量。
在Fox
类中,存在两个具有兼容参数的play()
方法:
public String play(Dog d) { // inherited from the parent class
public String play(Fox f) { // declared in Fox
编译器必须为方法的调用上下文选择更具体的方法
它标识了一个更具体的方法,另一个方法用于Dog
声明的类型参数:public String play(Dog d)
。
因此编译器在编译类时将play()
方法调用绑定到public String play(Dog d)
。
在运行时,调用具体实例的String play(Dog d)
方法。
至于第一种情况,foxi
变量引用Fox的一个实例,但Fox不会覆盖String play(Dog d)
。
所以JVM调用Dog的public String play(Dog d)
方法
然后,当执行f
并f.bark()
引用f
实例时,它会调用有效类型Fox
的方法。
所以再次输出“WuffRingding”。
要避免这种意外,您应该添加@Override
在用于覆盖父类方法的方法中:
例如:
@Override
public String play(Fox f) {
return "Ringding" + f.bark();
}
如果方法没有有效地覆盖层次结构中的play(Fox f)
方法,编译器会抱怨它。
答案 1 :(得分:1)
在您的情况下,play
方法已重载但未被覆盖。
执行此操作Dog d = new Fox()
Dog
的引用只会调用类Dog
的方法,除非Dog
类的方法被{{1}覆盖} .class。当重写方法时,在运行时解析调用此类方法,但在编译时解析重载方法的调用。
读取静态多态性和运行时多态性以进一步清除。
答案 2 :(得分:1)
显然,引起混淆的是你认为子类Fox
中的play方法会覆盖超类的play方法,而实际只是超载它。
如果在Fox-class的play-method中将f
的参数类型更改为Dog
,则输出将为" RingdingRingding"两次出于你在问题中分析的原因,因为在这种情况下,play-method正确地覆盖了超类方法。
让我们更详细地研究重载的播放方法:
hybrid.play(foxi)
编译器查看声明的hybrid
静态类型Dog
。 foxi
被声明为Fox
。因此,编译器在类Dog
中查找播放方法,该方法需要一个静态类型Fox
的参数。它不成功,因为只有一个play方法需要一个静态类型Dog
的参数。但是,编译器最后仍会选择此方法进行调用,因为Dog
是Fox
的超类。
foxi.play(hybrid)
编译器查看声明的foxi
静态类型Fox
。
hybrid
被声明为Dog
。因此,编译器在类Fox
中查找播放方法,该方法需要一个静态类型Dog
的参数。编译器可以在类Fox
中选择两种播放方法:play(Fox f)
和继承的重载方法play(Dog d)
。 play(Fox f)
不是有效选择,因为此方法需要一个Fox
类型的参数,而hybrid
则声明为Dog
。这意味着编译器将再次选择play(Dog d)
方法,该方法在类Dog
中声明,与之前的语句一样。
编译器不允许您使用play(Fox f)
覆盖play(Dog d)
的原因如下:想象一下,这是允许的,有人会这样做:
Dog doggo = new Dog();
hybrid.play(doggo);
现在,在运行时使用类型为play(Fox f)
的输入参数调用覆盖Dog
方法,该方法不起作用,因为实现了
play(Fox f)
预计f
不仅是Dog
,还有更专业的Fox
。
为了避免过载/覆盖混淆,请注释应该使用@Override
覆盖超类方法的方法。如果with @Override
带注释的方法实际上没有覆盖超类方法,编译器将拒绝编译您的代码。
答案 3 :(得分:1)
确定将调用哪种方法的规则是pretty complicated,但我会在这里尝试总结这些方法。
首先,对于for ti, tw in enumerate(text_words):
try:
wi = words.index(tw)
except ValueError:
pass
else:
text_words[ti] = replacement[wi]
print(' '.join(text_words))
# Result:
# Lorem Ipsum WORD simply dummy text of LIST printing TEXT typesetting industry. Lorem Ipsum has been LIST industry's standard dummy text ever
:
确定要搜索的类或接口
hybrid.play(foxi)
的类型为hybrid
,因此将在Dog
界面中搜索方法。这意味着只有Dog
上定义的方法才会被调用。这些是:
Dog
确定方法签名
您正在调用bark()
play(Dog)
方法,其参数类型为play
。 Fox
是Fox
的子类型,因此Dog
方法匹配。
因此,该方法是被调用的方法。
接下来,play(Dog)
:
确定要搜索的类或接口
foxi.play(hybrid)
的类型为foxi
,因此将在Fox
界面中搜索方法。可用的方法是:
Fox
请注意bark()
play(Dog)
play(Fox)
不会覆盖play(Fox)
:他们没有相同的方法签名,因此play(Dog)
只是一个过载。
确定方法签名
您正在调用play(Fox)
方法,其参数类型为play
。因此,Dog
方法是被调用的方法,因为这是唯一匹配的方法。
play(Dog)
具有运行时类型hybrid
并不重要:要调用的方法选择在编译时发生。
因此,Fox
,而不是play(Dog)
被调用。