2017-02-14 55 views
5

标题可能会引起误解,但作为一个非本地人我不能找出一个更好的。类的引用类型和实际的类类型,它决定调用哪个方法?

说,我有两个班,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 它仍然引用s到一个Fox对象,但我仍然有这样的输出:

WuffRingding

第二个我得到了同样的问题,因为foxiFoxhybrid一个实例实际上是Fox实例(?无论什么参考,右)输出中应"RingdingRingding"但话又说回来,我得到:

WuffRingding

有人可以解释为什么吗?

+1

重要的是'Fox.play(Fox)'不会覆盖'Dog.play(Dog)'。因为'hybrid'的类型是'Dog',所以'play(Dog)'超载被调用。 –

+0

@AndyTurner嗨安迪,谢谢你的回答。仍然当谈到类型转换时,实体类型是否正确?比方说,我把(狗)投给Fox的一个实例并且调用树皮方法,它仍然是Fox类中的方法,对吧?我在参考和铸造之间有点混淆.. –

+1

要调用的方法在编译时确定,而不是运行时。因此,如果您将一个对Dog的引用传递给某个方法,则会调用“Dog”过载,即使在运行时引用了Fox。 –

回答

2

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

您有两次:编译时间和运行时间。
这两次的规则不一样。

  • 在编译时,编译器必须静态确定哪个方法的确切签名被调用来编译好。
    该绑定是静态的,因为编译器无关于调用方法的具体实例,而且对于传递给该方法的参数也是一样的。
    编译器不依赖于有效类型,因为在运行时有效类型可能会在执行流程中发生更改。
    因此,编译器在可用的方法中搜索一个声明的类型,这是根据传递给它的声明的参数类型更具体的方法。

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

1)对于第一种情况:

Fox foxi = new Fox(); 
Dog hybrid = new Fox(); 
System.out.println(hybrid.play(foxi)); // Output number 1 
  • 第一次(编译时间):

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

在狗类,单个方法play()与相容的签名存在:

public String play(Dog d) { 

所以被用于结合此签名:String play(Dog d)

关于bark()方法,由于只有一个bark()方法签名,这一点非常明显。
因此,我们必须对在编译时

  • 第二次时(runtime)结合的方法,没有歧义:被调用的具体实例的方法String play(Dog d)

在运行时。 hybrid变量是指Fox的一个实例,但Fox不会覆盖 String play(Dog d)。福克斯定义了play()方法,但与其他签名:

public String play(Fox f) { 

所以JVM调用狗的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.bark()执行时调用f的有效类型的方法,而f引用Fox实例。

所以再次输出“WuffRingding”。


为了避免这种意外的你应该增加在设计来覆盖父类的方法方法@Override
例如:

@Override 
public String play(Fox f) { 
    return "Ringding" + f.bark(); 
} 

如果该方法不能有效覆盖一play(Fox f)方法在层次结构中,编译器会抱怨它。

+0

谢谢你的回答。仍然尝试了一些东西,如: 狗a =(狗)狐狸; a.bark(); 即使“a”类型的“Dog”类型的引用仍然是“Ringing”,您能解释原因吗? –

+1

不客气:)重新阅读我的答案的这一部分:“关于树皮()方法,它是显而易见的,因为只有一个bark()方法签名。 所以我们没有模糊的方法是绑定在编译时间“。这意味着在编译时,唯一的'bark()'方法被绑定在编译的类中。在运行时,JVM使用调用方法的有效实例的绑定方法('bark()')。 – davidxxx

+0

我明白了,非常感谢! –

1

在这里你的情况play方法超载未覆盖。

当你做到这一点Dog d = new Fox()Dog参考将调用Dog类的方法只有等到除非Dog类的方法是通过Fox类中重写。当方法被重写时,调用这些方法在运行时被解析,但重载方法的调用在编译时被解析。

读取静态多态性和运行时多态性以进一步清除。

1

很显然,这会导致你的困惑的事情是,你想播放方法在子类Fox覆盖超类的播放方法,而它实际上只是重载它。

如果更改f在福克斯级的播放方法输入Dog参数类型,输出将是“RingdingRingding”两次都为你在你的问题分析了原因,因为在这种情况下播放方法正确地重写超类方法。

让我们来看看与更详细的重载播放方法的情况下:

hybrid.play(foxi) 

编译器着眼于声明的静态类型的hybrid这是Dogfoxi被宣布为Fox。因此,编译器会寻找类Dog中的播放方法,该方法需要静态类型为Fox的一个参数。这是不成功的,因为只有一种播放方法需要静态类型Dog的一个参数。但是,编译器最终仍然会选择此方法进行调用,因为DogFox的超类。

foxi.play(hybrid) 

编译器着眼于声明的静态类型的foxi这是Foxhybrid被宣布为Dog。因此,编译器会在类Fox中查找播放方法,该方法需要静态类型为Dog的一个参数。编译器可以选择Fox类中的两种播放方法:play(Fox f)和继承的重载方法play(Dog d)play(Fox f)不是有效的选择,因为此方法需要Fox类型的一个参数,而hybrid被声明为Dog。这意味着编译器将再次选择类Dog中声明的play(Dog d)方法,就像前面的语句一样。

之所以编译器不允许您覆盖play(Fox f)play(Dog d)如下:想象一下,它允许有人这样做:

Dog doggo = new Dog(); 
hybrid.play(doggo); 

现在,重写play(Fox f)方法将与所谓的输入参数的类型Dog在运行时不起作用,因为实施 play(Fox f)预计f不仅是Dog,但更专门的Fox

为避免重载/覆盖混淆注释方法,它应覆盖超类方法@Override。如果带有注释的方法实际上不覆盖超类方法,编译器将拒绝编译您的代码。

1

决定调用哪个方法的规则是pretty complicated,但我会在这里尝试总结这些情况。

首先,对于hybrid.play(foxi)

  1. 确定类或接口搜索

    hybrid具有类型Dog,所以Dog接口将被搜索的方法。这意味着只有在Dog上定义的方法才可能被调用。它们是:

    bark() 
    play(Dog) 
    
  2. 确定方法签名

    要调用的方法play,与Fox类型的参数。 FoxDog的子类型,所以play(Dog)方法是匹配的。

因此,该方法是一个被调用的方法。

接着,对foxi.play(hybrid)

  1. 确定类或接口搜索

    foxi具有类型Fox,所以Fox接口将被搜索的方法。可用的方法是:

    bark() 
    play(Dog) 
    play(Fox) 
    

    注意play(Fox)不会覆盖play(Dog):他们不具有相同的方法签名,所以play(Fox)仅仅是超载。

  2. 确定方法签名

    要调用的方法play,与Dog类型的参数。因此,play(Dog)方法是被调用的方法,因为这是唯一匹配的方法。

    hybrid有一个运行时间类型Fox这并不重要:要调用的方法的选择发生在编译时。因此,play(Dog)而不是play(Fox)被调用。

相关问题