Class引用类型和实际class类型,哪个决定调用哪个方法?
Class type of reference and the actual class type, which decides which method to call?
标题可能具有误导性,但作为 non-native 我想不出更好的标题。
假设我有两个 类、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
object,但我仍然得到这个输出:
WuffRingding
第二个我遇到了同样的问题,因为 foxi
是 Fox
的一个实例,而 hybrid
实际上是 Fox
的一个实例(无论是什么参考,对吗?),输出应该是 "RingdingRingding"
但话又说回来,我得到了:
WuffRingding
有人可以解释为什么吗?
在你的情况下 play
方法被重载而不是被覆盖。
当您执行此操作时 Dog d = new Fox()
Dog
的引用将仅调用 class Dog
的方法,除非 Dog
class 被 Fox
class 覆盖。当方法被覆盖时,此类方法的调用在运行时解析,但重载方法的调用在编译时解析。
阅读静态多态和run-time多态以进一步清除。
显然,引起您困惑的是您认为子classFox
中的play-method覆盖了[= superclass 的 72=],而它实际上只是 重载 它。
如果将Fox-class的play-method中的f
的参数类型改为Dog
,两次输出都是"RingdingRingding"由于您在问题中分析的原因,因为在这种情况下 play-method 正确地覆盖了 superclass-method.
让我们更详细地研究 play-method 超载的情况:
hybrid.play(foxi)
编译器查看 hybrid
声明的静态类型,即 Dog
。 foxi
声明为 Fox
。因此,编译器在 class Dog
中寻找一个 play-method,它需要一个静态类型的参数 Fox
。它不成功,因为只有一个 play-method 需要一个静态类型的参数 Dog
。但是,编译器最终还是会选择这个方法来调用,因为Dog
是Fox
.
的superclass
foxi.play(hybrid)
编译器查看 foxi
声明的静态类型,即 Fox
。
hybrid
声明为 Dog
。因此,编译器在 class Fox
中寻找一个 play-method,它需要一个静态类型的参数 Dog
。 classFox
中有两个play-method编译器可以选择:play(Fox f)
和继承的重载方法play(Dog d)
。 play(Fox f)
不是一个有效的选择,因为此方法需要一个类型为 Fox
的参数,而 hybrid
被声明为 Dog
。这意味着编译器将再次选择 play(Dog d)
方法,它在 class Dog
中声明,就像前面的语句一样。
编译器不允许您用 play(Dog d)
覆盖 play(Fox f)
的原因如下:假设它被允许并且有人会这样做:
Dog doggo = new Dog();
hybrid.play(doggo);
现在,覆盖的 play(Fox f)
方法将在运行时使用 Dog
类型的 input-parameter 调用,但该方法不起作用,因为
play(Fox f)
期望 f
不仅是 Dog
,而且是更专业的 Fox
。
为了避免 overload/override 混淆注释应该用 @Override
覆盖 superclass-method 的方法。如果带有 @Override
注释的方法实际上没有覆盖 superclass 方法,编译器将拒绝编译您的代码。
确定将调用哪个方法的规则是pretty complicated,但我将在此处针对这种情况尝试对其进行总结。
首先,对于hybrid.play(foxi)
:
确定Class或搜索界面
hybrid
具有类型 Dog
,因此将在 Dog
接口中搜索方法。这意味着甚至可能只调用您在 Dog
上定义的方法。它们是:
bark()
play(Dog)
确定方法签名
您正在调用 play
方法,参数类型为 Fox
。 Fox
是Dog
的子类型,所以匹配play(Dog)
方法
因此,调用的是该方法。
接下来,foxi.play(hybrid)
:
确定Class或搜索界面
foxi
具有类型 Fox
,因此将在 Fox
接口中搜索方法。可用的方法是:
bark()
play(Dog)
play(Fox)
请注意 play(Fox)
不会覆盖 play(Dog)
:它们没有相同的方法签名,因此 play(Fox)
只是一个重载。
确定方法签名
您正在调用 play
方法,参数类型为 Dog
。因此,play(Dog)
方法是被调用的方法,因为这是唯一匹配的方法。
hybrid
具有运行时类型并不重要 Fox
:要调用的方法的选择发生在编译时。
因此,调用的是 play(Dog)
,而不是 play(Fox)
。
方法调用的两个重要事项。
你有两个时间:编译时间和运行时间。
而且这两次的规则也不一样。
在编译时,编译器必须静态地确定调用哪个方法的确切签名才能正常编译。
这种绑定是静态的,因为编译器不关心调用方法的具体实例,传递给方法的参数也是一样的。
编译器不依赖有效类型,因为在运行时有效类型可能会在执行流程中发生变化。
因此,编译器在可用方法中搜索声明类型,根据传递给它的参数声明类型,这是更具体的方法。
在运行时,根据调用该方法的有效实例,将使用来自 class 或另一个实例方法的实例方法。
但是调用的方法必须遵守编译时指定的签名。
1)第一种情况:
Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(hybrid.play(foxi)); // Output number 1
- 第一次(编译时间):
对于 Dog 实例,编译器必须找到最具体的方法play()
,并将 Fox 声明类型的变量作为参数。
在 Dog class 中,存在具有兼容签名的单个方法 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.bark()
执行时调用d
有效类型的方法,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
class中,存在两个具有兼容参数的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)
。
因此编译器在编译 class.
时将 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.bark()
执行时调用f
有效类型的方法,f
引用一个Fox
实例。
所以"WuffRingding"又输出了
为避免这种意外,您应该添加 @Override
在旨在覆盖父 class 方法的方法中:
例如:
@Override
public String play(Fox f) {
return "Ringding" + f.bark();
}
如果该方法没有有效地重写层次结构中的 play(Fox f)
方法,编译器会报错。
标题可能具有误导性,但作为 non-native 我想不出更好的标题。
假设我有两个 类、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
object,但我仍然得到这个输出:
WuffRingding
第二个我遇到了同样的问题,因为 foxi
是 Fox
的一个实例,而 hybrid
实际上是 Fox
的一个实例(无论是什么参考,对吗?),输出应该是 "RingdingRingding"
但话又说回来,我得到了:
WuffRingding
有人可以解释为什么吗?
在你的情况下 play
方法被重载而不是被覆盖。
当您执行此操作时 Dog d = new Fox()
Dog
的引用将仅调用 class Dog
的方法,除非 Dog
class 被 Fox
class 覆盖。当方法被覆盖时,此类方法的调用在运行时解析,但重载方法的调用在编译时解析。
阅读静态多态和run-time多态以进一步清除。
显然,引起您困惑的是您认为子classFox
中的play-method覆盖了[= superclass 的 72=],而它实际上只是 重载 它。
如果将Fox-class的play-method中的f
的参数类型改为Dog
,两次输出都是"RingdingRingding"由于您在问题中分析的原因,因为在这种情况下 play-method 正确地覆盖了 superclass-method.
让我们更详细地研究 play-method 超载的情况:
hybrid.play(foxi)
编译器查看 hybrid
声明的静态类型,即 Dog
。 foxi
声明为 Fox
。因此,编译器在 class Dog
中寻找一个 play-method,它需要一个静态类型的参数 Fox
。它不成功,因为只有一个 play-method 需要一个静态类型的参数 Dog
。但是,编译器最终还是会选择这个方法来调用,因为Dog
是Fox
.
foxi.play(hybrid)
编译器查看 foxi
声明的静态类型,即 Fox
。
hybrid
声明为 Dog
。因此,编译器在 class Fox
中寻找一个 play-method,它需要一个静态类型的参数 Dog
。 classFox
中有两个play-method编译器可以选择:play(Fox f)
和继承的重载方法play(Dog d)
。 play(Fox f)
不是一个有效的选择,因为此方法需要一个类型为 Fox
的参数,而 hybrid
被声明为 Dog
。这意味着编译器将再次选择 play(Dog d)
方法,它在 class Dog
中声明,就像前面的语句一样。
编译器不允许您用 play(Dog d)
覆盖 play(Fox f)
的原因如下:假设它被允许并且有人会这样做:
Dog doggo = new Dog();
hybrid.play(doggo);
现在,覆盖的 play(Fox f)
方法将在运行时使用 Dog
类型的 input-parameter 调用,但该方法不起作用,因为
play(Fox f)
期望 f
不仅是 Dog
,而且是更专业的 Fox
。
为了避免 overload/override 混淆注释应该用 @Override
覆盖 superclass-method 的方法。如果带有 @Override
注释的方法实际上没有覆盖 superclass 方法,编译器将拒绝编译您的代码。
确定将调用哪个方法的规则是pretty complicated,但我将在此处针对这种情况尝试对其进行总结。
首先,对于hybrid.play(foxi)
:
确定Class或搜索界面
hybrid
具有类型Dog
,因此将在Dog
接口中搜索方法。这意味着甚至可能只调用您在Dog
上定义的方法。它们是:bark() play(Dog)
确定方法签名
您正在调用
play
方法,参数类型为Fox
。Fox
是Dog
的子类型,所以匹配play(Dog)
方法
因此,调用的是该方法。
接下来,foxi.play(hybrid)
:
确定Class或搜索界面
foxi
具有类型Fox
,因此将在Fox
接口中搜索方法。可用的方法是:bark() play(Dog) play(Fox)
请注意
play(Fox)
不会覆盖play(Dog)
:它们没有相同的方法签名,因此play(Fox)
只是一个重载。确定方法签名
您正在调用
play
方法,参数类型为Dog
。因此,play(Dog)
方法是被调用的方法,因为这是唯一匹配的方法。hybrid
具有运行时类型并不重要Fox
:要调用的方法的选择发生在编译时。因此,调用的是
play(Dog)
,而不是play(Fox)
。
方法调用的两个重要事项。
你有两个时间:编译时间和运行时间。
而且这两次的规则也不一样。
在编译时,编译器必须静态地确定调用哪个方法的确切签名才能正常编译。
这种绑定是静态的,因为编译器不关心调用方法的具体实例,传递给方法的参数也是一样的。
编译器不依赖有效类型,因为在运行时有效类型可能会在执行流程中发生变化。
因此,编译器在可用方法中搜索声明类型,根据传递给它的参数声明类型,这是更具体的方法。在运行时,根据调用该方法的有效实例,将使用来自 class 或另一个实例方法的实例方法。
但是调用的方法必须遵守编译时指定的签名。
1)第一种情况:
Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(hybrid.play(foxi)); // Output number 1
- 第一次(编译时间):
对于 Dog 实例,编译器必须找到最具体的方法play()
,并将 Fox 声明类型的变量作为参数。
在 Dog class 中,存在具有兼容签名的单个方法 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.bark()
执行时调用d
有效类型的方法,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
class中,存在两个具有兼容参数的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)
。
因此编译器在编译 class.
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.bark()
执行时调用f
有效类型的方法,f
引用一个Fox
实例。
所以"WuffRingding"又输出了
为避免这种意外,您应该添加 @Override
在旨在覆盖父 class 方法的方法中:
例如:
@Override
public String play(Fox f) {
return "Ringding" + f.bark();
}
如果该方法没有有效地重写层次结构中的 play(Fox f)
方法,编译器会报错。