Home > Enterprise >  Invoking a Method of the Superclass expecting a Superclass argument on a Subclass instance by passin
Invoking a Method of the Superclass expecting a Superclass argument on a Subclass instance by passin

Time:06-05

Can anyone please explain the output of the following code, and what's the Java principle involved here?

class Mammal {
    void eat(Mammal m) {
        System.out.println("Mammal eats food");
    }
}

class Cattle extends Mammal{
    void eat(Cattle c){
        System.out.println("Cattle eats hay");
    }
}

class Horse extends Cattle {
    void eat(Horse h) {
        System.out.println("Horse eats hay");
    }
}

public class Test {
    public static void main(String[] args) {
        Mammal h = new Horse();
        Cattle c = new Horse();
        c.eat(h);
    }
}

It produces the following output:

Mammal eats food

I want to know how we are coming at the above result.

CodePudding user response:

Overloading vs Overriding

That's not a valid method overriding, because all the method signatures (method name parameters) are different:

void eat(Mammal m)
void eat(Cattle c)
void eat(Horse h)

That is called method overloading (see) and class Horse will have 3 distinct methods, not one. I.e. its own overloaded version of eat() and 2 inherited versions.

The compiler will map the method call c.eat(h) to the most specific method, which is eat(Mammal m), because variable h is of type Mammal.

In order to invoke the method with a signature eat(Horse h) you need to coerce h into the type Horse. Note, that such conversion would be considered a so-called narrowing conversion, and it will never happen automatically because there's no guarantee that such type cast will succeed, so the compiler will not do it for you.

Comment out the method void eat(Mammal m) and you will see the compilation error - compilers don't perform narrowing conversions, it can only help you with widening conversions because they guaranteed to succeed and therefore are safe.

That what would happen if you'll make type casting manually:

Coercing h into the type Horse:

c.eat((Horse) h);

Output:

Cattle eats hay   // because `c` is of type `Cattle` method `eat(Cattle c)` gets invoked

Because variable c is of type Cattle it's only aware of the method eat(Cattle c) and not eat(Horse h). And behind the scenes, the compiler will widen the h to the type Cattle.


Coercing both c and h into the type Horse:

((Horse) c).eat((Horse) h);

Output:

Horse eats hay   // now `eat(Horse h)` is the most specific method

Rules of Overriding

The rules of method overriding conform to the Liskov substitution principle.

Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.

The child class should declare its behavior in such a way so that it can be used everywhere where its parent is expected:

  • Method signatures must match exactly. I.e. method names should be the same as the types of parameters. And parameters should be declared in the same order.

  • The access modifier of an overridden method can be the same or wider, but it can not be more strict. I.e. protected method in the parent class can be overridden as public or can remain protected, we can not make it private.

  • Return type of an overridden method should be precisely the same in case primitive type. But if a parent method declares to return a reference type, its subtype can be returned. I.e. if parent returns Number an overridden method can provide Integer as a return type.

  • If parent method declares to throw any checked exceptions then overridden method is allowed to declare the same exceptions or their subtypes, can be implemented as safe (or not throwing exceptions at all). It's not allowed to make the overridden method less safe than the method declared by the parent, i.e. to throw checked exceptions not declared by the parent method. Note there are no restrictions regarding runtime exceptions (unchecked), overridden methods are free to declare them even if they are not specified by the parent method.

This would be a valid example of method overriding:

static class Mammal{
    void eat(Mammal m){
        System.out.println("Mammal eats food");
    }
}

public class Cattle extends Mammal{
    @Override
    void eat(Mammal c) {
        System.out.println("Cattle eats hay");
    }
}

public class Horse extends Cattle{
    @Override
    public void eat(Mammal h) throws RuntimeException {
        System.out.println("Horse eats hay");
    }
}

main()

public static void main(String[] args) {
    Mammal h = new Horse();
    Cattle c = new Horse();
    c.eat(h);
}

Output:

Horse eats hay

CodePudding user response:

In your example, method overloading occurs(same method name but different parameter type passed).

When you're calling c.eat(h), the compiler will know that you want to use the void eat(Mammal m) method since your h reference has the type Mammal.

If you would change the object reference to Horse or Cattle like so:

Horse h = new Horse();

The output will be:

Cattle eats hay

This happens because the compiler will use the most specific method, in this case void eat(Cattle c), based on the object reference type Horse.

You may also be interested in method overriding which uses runtime polymorphism.

  • Related