The class type of the link and the actual class type that determines which method to call?

The name may be misleading, but as a step-mother I could not find a better one.

Let's 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 several instances and also 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 1. Ouput, I expected "RingdingRingding" because the hybrid actually a reference to a Dog instance, even if the reference is of type Dog it still refers to a Fox object, but still I got this output:

Wuffring

Secondly, I have the same problem, since foxi is an instance of Fox and hybrid is actually an instance of Fox (no matter what link, right?), The output should be "RingdingRingding" , but again, I got:

Wuffring

Can someone explain why?

+5
source share
4 answers

Two important things to call a method.

You have two times: compile time and runtime.
And the rules do not match between the two times.

  • at compile time, it must be determined statically which exact method signature is invoked to compile.
    This binding is static because the compiler does not matter for the specific instance on which the method is called, and it is the same for the parameters passed to the method.
    The compiler does not rely on efficient types, since at run time, effective types can change during the execution thread.
    Thus, the compiler searches among the available methods for the declared type, which is a more specific method in accordance with the declared types of the parameters passed to it.

  • at run time, an instance method from a class or from another will be used in accordance with the effective instance on which the method is called.
    But the called method must 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 (compilation time):

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

In the Dog class, there is one play() method with a compatible signature:

 public String play(Dog d) { 

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

The bark() method is very obvious, since there is only one signature of the bark () method.
Thus, we have no ambiguity in the method that is associated at compile time

  • Second time (runtime):

At run time, the String play(Dog d) method of the specific instance is called. The hybrid variable refers to an instance of Fox, but Fox does not override String play(Dog d) . Fox defines the play () method, but with a different signature:

 public String play(Fox f) { 

So, the JVM calls the public String play(Dog d) { dog method.
And then it calls an efficient type d method when d.bark() is d.bark() , and d refers to the Fox instance.

So, "WuffRingding" is displayed.


2) For the second case:

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

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

In the Fox class, there are two play() methods with a compatible parameter:

 public String play(Dog d) { // inherited from the parent class public String play(Fox f) { // declared in Fox 

The compiler must choose a more specific method for the method call context
It identifies a more specific method than another for the Dog parameter of the declared type: public String play(Dog d) . Therefore, the compiler binds the call to the play() method to public String play(Dog d) when compiling the class.

  • Second time (runtime):

At run time, the String play(Dog d) method of the specific instance is called.
As for the first case, the foxi variable refers to an instance of Fox, but Fox does not override String play(Dog d) .
Thus, the JVM calls the dog’s public String play(Dog d) method.
And then it calls an efficient type f method when f.bark() is f.bark() , and f refers to an instance of Fox .

This again displays "WuffRingding".


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

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

If the method does not override the play(Fox f) method in the hierarchy, the compiler will complain about it.

+2
source

Here, in your case, the play method is overloaded with no overrides.

When you do this Dog d = new Fox() The Dog link will only call the Dog class method until the Dog class methods are overridden by the Fox class. When methods are overridden, calls to such methods are allowed at runtime, but calls to overloaded methods are allowed at compile time.

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

+1
source

Apparently, the thing that is causing your confusion is that you think that the playback method in the Fox subclass overrides the playback method of the superclass, whereas in reality it is just overloading this.

If you change the type of the parameter f in the Fox class's playback method to type Dog , the output will be "RingdingRingding" both times for the reasons that you analyzed in your question, because this is if the game method correctly overrides the superclass method.

Take a closer look at the case of overloaded game methods:

 hybrid.play(foxi) 

The compiler looks at the declared static hybrid type, which is equal to Dog . foxi declared as Fox . Therefore, the compiler is looking for a playback method in the Dog class, which expects a single parameter of a Fox type static type. This fails because there is only a game method that expects a single parameter of the static type Dog . However, the compiler will still choose this method to call at the end, because Dog is a Fox superclass.

 foxi.play(hybrid) 

The compiler looks at the declared static type foxi , which is equal to Fox . hybrid declared as Dog . Therefore, the compiler is looking for a playback method in the Fox class that expects a single parameter of the static type Dog . There are two game methods in the Fox class that the compiler can choose from: play(Fox f) and the inherited overloaded play(Dog d) method. play(Fox f) not a valid choice, as this method expects a single parameter of type Fox , and the hybrid declared as Dog . This means that the compiler will again select the play(Dog d) method declared in the Dog class, as for the previous statement.

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

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

Now the overriden play(Fox f) method will be called with an input parameter of type Dog at run time, which does not work, since the implementation of play(Fox f) expects f be not only Dog , but also a more specialized Fox .

To avoid intercepting / overriding confusion, annotate methods that should override superclass methods with @Override . The compiler will refuse to compile your code if the annotated @Override method does not actually override the superclass method.

+1
source

The rules for determining which method will be called are quite complex , but I will try to summarize them for these cases here.

Firstly, for hybrid.play(foxi) :

  • Define a class or interface to search

    hybrid is of type Dog , so the Dog interface will look for methods. This means that even the methods that you define on Dog can even be called. It:

     bark() play(Dog) 
  • Define Method Signature

    You call the play method with a parameter of type Fox . Fox is a subtype of Dog , so the play(Dog) method maps.

So this method is callable.

Next, for foxi.play(hybrid) :

  • Define a class or interface to search

    foxi is of type Fox , so the Fox interface will look for methods. The following methods are available:

     bark() play(Dog) play(Fox) 

    Note that play(Fox) does not override play(Dog) : they do not have the same method signature, so play(Fox) is just an overload.

  • Define Method Signature

    You call the play method with a parameter of type Dog . Thus, the play(Dog) method is the one that is called, since it is the only one that matches.

    It doesn't matter that hybrid has a Fox runtime type: this choice of method to call happens at compile time.

    Therefore, play(Dog) is called, not play(Fox) .

+1
source

Source: https://habr.com/ru/post/1264203/


All Articles