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

时间:2017-02-14 12:54:03

标签: java inheritance overloading method-overriding

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

假设我有两个课程,DogFox

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

第二个我遇到了同样的问题,因为foxiFox的实例而hybrid实际上是Fox的一个实例(无论参考什么,对吧?),输出应该是"RingdingRingding"但是又一次,我得到了:

  

WuffRingding

有人可以解释原因吗?

4 个答案:

答案 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) {方法 然后,当执行dd.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)方法 然后,当执行ff.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静态类型Dogfoxi被声明为Fox。因此,编译器在类Dog中查找播放方法,该方法需要一个静态类型Fox的参数。它不成功,因为只有一个play方法需要一个静态类型Dog的参数。但是,编译器最后仍会选择此方法进行调用,因为DogFox的超类。

foxi.play(hybrid)

编译器查看声明的foxi静态类型Foxhybrid被声明为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

  1. 确定要搜索的类或接口

    hybrid.play(foxi)的类型为hybrid,因此将在Dog界面中搜索方法。这意味着只有Dog上定义的方法才会被调用。这些是:

    Dog
  2. 确定方法签名

    您正在调用bark() play(Dog) 方法,其参数类型为playFoxFox的子类型,因此Dog方法匹配。

  3. 因此,该方法是被调用的方法。

    接下来,play(Dog)

    1. 确定要搜索的类或接口

      foxi.play(hybrid)的类型为foxi,因此将在Fox界面中搜索方法。可用的方法是:

      Fox

      请注意bark() play(Dog) play(Fox) 不会覆盖play(Fox):他们没有相同的方法签名,因此play(Dog)只是一个过载。

      < / LI>
    2. 确定方法签名

      您正在调用play(Fox)方法,其参数类型为play。因此,Dog方法是被调用的方法,因为这是唯一匹配的方法。

      play(Dog)具有运行时类型hybrid并不重要:要调用的方法选择在编译时发生。

      因此,Fox,而不是play(Dog)被调用。