Non-overridden subclass method with same name calling

Viewed 161
class Animal{
    void eat(Animal animal){
        System.out.println("animal eats animal");
    }
}

public class Dog extends Animal{
    void eat(Dog dog){
        System.out.println("dog eats dog");
    }

    public static void main(String[] args) {
        Animal a = new Dog();
        Dog b = new Dog();
        a.eat(b);
        b.eat(b);
    }
}

In the above code, the output will be

animal eats animal
dog eats dog

Why this happened?

5 Answers

Probably you expect to see twice "dog eats dog". This does not happen because the two methods have a different signature. Therefore, Dog#eat(Dog) does not override Animal#eat(Animal) but provides a more specific eat method instead.

If you add @Override to void eat(Dog dog) there will be an error. Using this annotation is good practice because it denotes that the annotated method should override a method declaration in a supertype. If the method does not do that (as in your example) you get the following error to make you aware if it:

Method does not override method from its superclass


If you want to override the eat method in Dog, you need to provide the same signature:

@Override
void eat(Animal animal) { // instead of eat(Dog dog)
    System.out.println("dog eats dog");
}

It's based on the concept of Inheritance and Polymorphism

Overriding happens when the sub-class has the same signature methods as that of the superclass. In your code, in the below subclass *method, the parameter being passed is a Dog type object and in the superclass i.e Animal, the parameter passed is an Animal type object.

void eat(Dog dog){
        System.out.println("dog eats dog");
    }

So you can change the above method as below to see the overriding effect: -

void eat(Animal dog){
        System.out.println("dog eats dog");
    }

As suggested by @Mat, it's best to use @Override annotation because it will help the java compiler to find the issue at the compile time itself.

Below I'm trying to explain the concept of how inheritance and polymorphism are working once you change the signature of the eat method in Dog class: -

Inheritance is a way to base one class on another class, like a template built from an existing template. You could create a class called 'Dog' that acts as a template for all Dog objects. We could then create another class called 'Animal' that is a parent class of our 'Dog' class. All Dogs are animals, but not all animals are dogs. Our Animal class could define functionality for all Animals and then the Dog class could take all this functionality, without re-writing it, by extending/inheriting from the Animal class. The Dog class could then add more functionality, more variables, and methods, that are specific only to Dog objects.

The Dog class extends the Animal class, this is inheritance. The Dog class is overwriting the Animal class eat method.

When we say Animal a = new Dog();, we declare a variable a that is declared as an Animal type, but it initialized as a Dog object. This is polymorphism. Because the Dog extends from the Animal class, we can treat it as an Animal, and declare it as an Animal variable type. We cannot do the reverse, because the Animal class does not extend from the Dog class (not all Animals are dogs)

This is because the a variable is being treated as an Animal data type. This is why you will be able to access all the Animal class methods but the methods with the same signature as Dog will be overridden by the implementation from the Dog class. Remember, the left side of the equals '=' symbol is the declaration and the right side is the initialization.

Also, that's why when you declare Dog b = new Dog(); and call the eat() method it calls the implementation from the Class Dog and not Animal as it's explicitly mentioned as object type Dog

It is simply because Java does not support contravariant parameters. On the other hand, it supports covariant return types. Due to the support for covariant return types, a subclass override can have a more specific return type in the hierarchy while overriding the base class method, like the code below is valid:

class Animal {
   protected Animal getAnimal() {
        System.out.println("Animal");
        return this;
   }
}
class Dog extends Animal {
  @Override 
  protected Dog getAnimal() {
     System.out.println("Dog");
     return this;
  }
}

In the above example, you can observe that Dog.getAnimal() returns a more specific Dog instead of the base Animal but it is still considered an override because Java supports covariant return types.

On the other hand, if you do that with parameters:

class Animal {
   protected void petAnimal(Animal animal) {
        System.out.println("Petting Animal");
   }
}
class Dog extends Animal {
  @Override 
  protected void petAnimal(Dog dog) {
     System.out.println("Petting Dog");
  }
}

This is not an override but an overload. Hence both the petAnimal() methods (one with Animal as parameter and another with Dog as parameter) are treated as two different methods. Remember, parameters are part of the method signature whereas return types are not.

The second example does not even work as the @Override annotation finds out that the method is not an override. Whenever you want to ensure that you override, use the @Override annotation, it will let you know if you are not overriding the method. @Override can also be used when implementing interfaces.

class Animal{
    void eat(Animal animal){
        System.out.println("animal eats animal");
    }
}

public class Dog extends Animal{
    void eat(Dog dog){
        System.out.println("dog eats dog");
    }

    public static void main(String[] args) {
        Animal a = new Dog(); //We use this when we don't know the exact runtime type of an object
        //Parent can hold any child but only parent specific methods will be called.
        Dog b = new Dog();
        a.eat(b); //Parent method will be called i.e Animal.eat(...)
        b.eat(b); //Dog Class method will be called i.e Dog.eat(...)
    }
}

First of all, the eat() not being overridden in the child class Dog

Usually, method overloading doesn't necessarily need inheritance and can be achieved within the same class. However, in this code the eat() method is overloaded by child class Dog.

Overloaded methods are differentiated by the number and the type of the arguments passed into the method.

So, at compile time it always picks the most specific class implementation based on its type.

Related