类类型的引用和实际的类类型,它决定调用哪个方法?

Anh*_*hạm 5 java inheritance overriding overloading

标题可能会产生误导,但作为非本地人,我无法找到更好的标题.

假设我有两个课程,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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我创建了一些实例,并调用了一些方法

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
Run Code Online (Sandbox Code Playgroud)

对于1. Ouput我的预期,"RingdingRingding"因为hybrid实际上是对一个实例Dog的引用,即使引用有一个类型Dog 仍然引用一个Fox对象,但我仍然得到了这个输出:

WuffRingding

第二个我得到了同样的问题,因为foxi是一个实例Fox并且hybrid实际上是一个实例Fox(无论什么参考,对吧?),输出应该是"RingdingRingding"然后再次,我得到:

WuffRingding

有人可以解释原因吗?

dav*_*xxx 2

方法调用有两件重要的事情。

\n\n

你有两个时间:编译时间和运行时间。
\n而且这两次的规则也不一样。

\n\n
    \n
  • 在编译时,编译器必须静态地确定调用哪个准确的方法签名才能正常编译。
    \n此绑定是静态的,因为编译器与调用该方法的具体实例无关,并且对于传递给该方法的参数也是如此。
    \n编译器不依赖有效类型,因为在运行时有效类型可能会在执行流程中发生变化。
    \n因此,编译器在可用方法中搜索声明类型,这是根据传递给它的参数的声明类型的更具体的方法。

  • \n
  • 在运行时,将根据调用该方法的有效实例来使用来自一个类或另一个类的实例方法。
    \n但是调用的方法必须遵守编译时指定的签名。

  • \n
\n\n

1)对于第一种情况:

\n\n
Fox foxi = new Fox();\nDog hybrid = new Fox();\nSystem.out.println(hybrid.play(foxi)); // Output number 1\n
Run Code Online (Sandbox Code Playgroud)\n\n
    \n
  • 第一次(comp\xc3\xaele时间):
  • \n
\n\n

对于 Dog 实例,编译器必须找到最具体的方法play(),并将具有 Fox 声明类型的变量作为参数。

\n\n

play()在 Dog 类中,存在具有兼容签名的单个方法:

\n\n
public String play(Dog d) {\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以这个签名用于绑定:String play(Dog d)

\n\n

关于bark()方法,很明显,因为只有一个 bark() 方法签名。
\n所以我们对编译时绑定的方法没有歧义

\n\n
    \n
  • 第二次(运行时):
  • \n
\n\n

在运行时,String play(Dog d)调用具体实例的方法。\n该hybrid变量引用 Fox 的实例,但 Fox 不会覆盖 \n String play(Dog d)。Fox 定义了一个 play() 方法,但有另一个签名:

\n\n
public String play(Fox f) {\n
Run Code Online (Sandbox Code Playgroud)\n\n

于是JVM调用了 public String play(Dog d) {Dog的方法。
\n然后调用dwhend.bark()执行时有效类型的方法并d引用一个Fox实例。

\n\n

因此输出“WuffRingding”。

\n\n
\n\n

2)对于第二种情况:

\n\n
Fox foxi = new Fox();\nDog hybrid = new Fox();\nSystem.out.println(foxi.play(hybrid)); // Output number 2\n
Run Code Online (Sandbox Code Playgroud)\n\n
    \n
  • 第一次(编译时):
  • \n
\n\n

例如Fox,编译器必须找到最具体的方法play(),并将具有声明类型的变量作为参数Dog

\n\n

Fox类中,play()存在两种具有兼容参数的方法:

\n\n
public String play(Dog d) { // inherited from the parent class\n\npublic String play(Fox f) { // declared in Fox\n
Run Code Online (Sandbox Code Playgroud)\n\n

编译器必须为方法的调用上下文选择更具体的方法\n它标识一个比声明的类型参数
的另一个方法更具体的方法: 。\n因此编译器在编译类时将方法调用绑定到。Dogpublic String play(Dog d)play()public String play(Dog d)

\n\n
    \n
  • 第二次(运行时):
  • \n
\n\n

在运行时,String play(Dog d)调用具体实例的方法。
\n对于第一种情况,foxi变量引用 Fox 的实例,但 Fox 不会覆盖String play(Dog d)
\n于是JVM调用了 public String play(Dog d)Dog的方法。
\n然后调用fwhenf.bark()执行时有效类型的方法并f引用一个Fox实例。

\n\n

所以“WuffRingding”再次输出。

\n\n
\n\n

为了避免这种意外,您应该在@Override设计用于重写父类方法的方法中添加\n:
\n例如:

\n\n
@Override\npublic String play(Fox f) {\n    return "Ringding" + f.bark();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果该方法没有有效地覆盖play(Fox f)层次结构中的方法,编译器将对此进行抱怨。

\n