5

The title may be misleading but as a non-native i couldn't figure out a better one.

Say i have two classes, Dog and 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();
    }
}

And i create some instances as well call some methods

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

For the 1. Ouput I expected "RingdingRingding" because hybrid actually is a reference to an instance of the Dog, even if the reference has a type of Dog it still refers to an Fox object, but still i got this output :

WuffRingding

The second one i got the same problem, since foxi is an instance of Fox and hybrid is actually an instance of Fox (no matter what reference, right ?), the ouput should be "RingdingRingding" but then again, i got :

WuffRingding

Can someone explain why ?

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • 1
    The significant thing is that `Fox.play(Fox)` doesn't override `Dog.play(Dog)`. Because the type of `hybrid` is `Dog`, the `play(Dog)` overload is invoked. – Andy Turner Feb 14 '17 at 12:59
  • @AndyTurner hi Andy, thanks for your answer. Still when it comes to typecasting, the actuall type does matter right ? Let say i cast (Dog) to an instance of Fox and call the bark method, still it will be the method in class Fox, right ? Im little confused between reference and casting.. – Anh Tuấn Phạm Feb 14 '17 at 13:01
  • 1
    The method to be invoked is determined at compile time, not runtime. So, if you pass a reference to a `Dog` to a method, the overload taking a `Dog` is invoked - even if that refers to a `Fox` at runtime. – Andy Turner Feb 14 '17 at 13:02
  • @AndyTurner i dont really get what you say. Then again why when i typecast (Dog) to an object of Fox, the method bark() in Fox will still be called because here it matters what the actual type of the object. Do you have some keywords so i can learn more about ? – Anh Tuấn Phạm Feb 14 '17 at 13:11

4 Answers4

2

Two important things for method invocations.

You have two times : the compilation time and the runtime.
And rules are not the same between these two times.

  • at compile time the compiler has to definite statically which exact signature of method is called to compile fine.
    This binding is static as the compiler doesn't matter of the concrete instance on which the method is invoked and it is the same thing for the parameters passed to the method.
    The compiler doesn't rely on the effective types as at runtime the effective types could change during the execution flow.
    So the compiler searches among available methods for a declared type, which is the more specific method according to the declared types of parameters passed to it.

  • at runtime an instance method from a class or from another one will be used according to the effective instance on which the method is invoked.
    But the invoked method has to respect the signature specified at compile time.

1)For the first case :

Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(hybrid.play(foxi)); // Output number 1
  • First time (compîle time) :

For a Dog instance, the compiler has to find the most specific method play() with as parameter a variable with the Fox declared type.

In Dog class, a single method play() with a compatible signature exist :

public String play(Dog d) {

So this signature is used for the binding : String play(Dog d).

About the bark() method, it is much obvious as there is only one bark() method signature .
So we have no ambiguity on the method that is bound at compile time

  • Second time (runtime) :

At runtime the String play(Dog d) method of the concrete instance is invoked. The hybrid variable refers to an instance of Fox but Fox doesn't override String play(Dog d). Fox defines a play() method but with another signature :

public String play(Fox f) {

So the JVM invokes the public String play(Dog d) { method of Dog.
And then it invokes the method of the effective type of d when d.bark() is executed and d refers to a Fox instance.

So "WuffRingding" is output.


2) For the second case :

Fox foxi = new Fox();
Dog hybrid = new Fox();
System.out.println(foxi.play(hybrid)); // Output number 2
  • First time (compile time) :

For a Fox instance, the compiler has to find the most specific method play() with as parameter a variable with the Dog declared type.

In Fox class, two play() methods with a compatible parameter exist :

public String play(Dog d) { // inherited from the parent class

public String play(Fox f) { // declared in Fox

The compiler has to choose the more specific method for the call context of the method
It identifies a method more specific that the other one for a Dog declared type parameter : public String play(Dog d). So the compiler binds the play() method invocation to public String play(Dog d) when it compiles the class.

  • Second time (runtime) :

At runtime the String play(Dog d) method of the concrete instance is invoked.
As for the first case, the foxi variable refers to an instance of Fox but Fox doesn't override String play(Dog d).
So the JVM invokes the public String play(Dog d) method of Dog.
And then it invokes the method of the effective type of f when f.bark() is executed and f refers to a Fox instance.

So "WuffRingding" is again output.


To avoid this kind of surprise you should add @Override in the methods designed to override a parent class method :
For example :

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

If the method doesn't override effectively a play(Fox f) method in the hierarchy, the compiler will complain about it.

davidxxx
  • 125,838
  • 23
  • 214
  • 215
  • Wow thanks for your answer. Still tried some thing like : Dog a = (Dog) foxi; a.bark(); Output is still "Ringding" even if "a" a reference of type "Dog" is, can you explain why ? – Anh Tuấn Phạm Feb 14 '17 at 14:42
  • 1
    You are welcome :) Re read this part of my answer : "About the bark() method, it is much obvious as there is only one bark() method signature . So we have no ambiguity on the method that is bound at compile time". It means that at compile time the unique `bark()` method was bound in the compiled class. And at runtime, the JVM uses the bound method (`bark()`) of the effective instance on which the method is invoked on. – davidxxx Feb 14 '17 at 14:49
  • i get it now, many thanks ! – Anh Tuấn Phạm Feb 14 '17 at 15:16
1

Here in your case The play method is overloaded not overridden.

When you do this Dog d = new Fox() The reference of Dog will call the method of class Dog only until and unless the methods of Dog class are overridden by Fox class. When the methods are overridden invoking of such methods is resolved at runtime but the invoking of overloaded methods is resolved at compile time.

Read static polymorphism and run-time polymorphism for further clearance.

BeginnersSake
  • 650
  • 2
  • 18
  • 30
1

Apparently, the thing which causes your confusion is that you think the play-method in the subclass Fox overrides the play-method of the superclass, while it actually just overloads it.

If you change the parameter type of f in the play-method of the Fox-class to type Dog, the output will be "RingdingRingding" both times for the reasons you analyzed in your question, because in this case the play-method correctly overrides the superclass-method.

Let's look into the case with the overloaded play-methods in more detail:

hybrid.play(foxi)

The compiler looks at the declared static type of hybrid which is Dog. foxi is declared as Fox. Therefore, the compiler looks for a play-method in class Dog which expects one parameter of static type Fox. It is not successful, because there is only a play-method which expects one parameter of static type Dog. However, the compiler will still pick this method for invocation in the end, because Dog is a superclass of Fox.

foxi.play(hybrid)

The compiler looks at the declared static type of foxi which is Fox. hybrid is declared as Dog. Therefore, the compiler looks for a play-method in class Fox which expects one parameter of static type Dog. There are two play-methods in class Fox the compiler can choose from: play(Fox f) and the inherited overloaded method play(Dog d). play(Fox f) is not a valid choice since this method expects one parameter of type Fox, while hybrid is declared as Dog. This means the compiler will again pick the play(Dog d) method, which is declared in class Dog, like for the previous statement.

The reason why the compiler doesn't allow you to override play(Fox f) with play(Dog d) is the following: Imagine it was allowed and someone would do this:

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

Now, the overriden play(Fox f) method would be called with an input-parameter of type Dog at runtime which doesn't work, because the implemenatation of play(Fox f) expects that the f is not only a Dog, but a more specialized Fox.

To avoid overload/override confusion annotate methods which should override superclass-methods with @Override. The compiler will refuse to compile your code if a with @Override annotated method actually does not override a superclass method.

Calculator
  • 2,769
  • 1
  • 13
  • 18
1

The rules for determining which method will be invoked are pretty complicated, but I'll try to summarize them for this cases here.

Firstly, for hybrid.play(foxi):

  1. Determine Class or Interface to Search

    hybrid has type Dog, so the Dog interface will be searched for methods. This means that only methods you have defined on Dog will even possibly be invoked. These are:

    bark()
    play(Dog)
    
  2. Determine Method Signature

    You are invoking the play method, with a parameter of type Fox. Fox is a subtype of Dog, so the play(Dog) method is matched.

So, that method is the one invoked.

Next, for foxi.play(hybrid):

  1. Determine Class or Interface to Search

    foxi has type Fox, so the Fox interface will be searched for methods. The available methods are:

    bark()
    play(Dog)
    play(Fox)
    

    Notice that play(Fox) doesn't override play(Dog): they don't have the same method signature, so play(Fox) is merely an overload.

  2. Determine Method Signature

    You are invoking the play method, with a parameter of type Dog. As such, the play(Dog) method is the one invoked, since that is the only one which matches.

    It doesn't matter that hybrid has a runtime type Fox: this selection of the method to be invoked happens at compile time.

    Hence, play(Dog), not play(Fox), is invoked.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243