Nick
Nick

Reputation: 158

Why does java allow for overriding methods with subclass return types but not subclass parameters?

If I have an abstract (or any as far as I know) superclass in java like so:

public abstract class Person {
    public abstract Person getPerson(Person p);
}

I noticed that a subclass can use itself for the return type, but not the parameter when overriding this method:

public class Child extends Person {
    @Override
    public Child getPerson(Person p) { //Works fine
        return this;
    }

    @Override
    public Person getPerson(Child p) { //Doesn't work
        return this;
    }
}

Why is this not allowed? I'm not asking for a solution - I know I could just use an instanceof check in the method or something, I'm more asking why the former is acceptable and the latter is not to the compiler.

Upvotes: 1

Views: 887

Answers (3)

Amal K
Amal K

Reputation: 4929

When parameters allow doing this, they are called contravariant parameters. When you do it with return types, they are called covariant return types. Java supports covariant return types but not contravariant parameters.

That would change the method signature and it will no longer be an override. Return types are not part of the method signature but the type and the number of parameters of the method are part of the signature, hence this would interfere with overloading.

Moreover, if Java allowed you to do that, that would cause unexpected behavior in some cases and break runtime polymorphism with virtual methods because you are narrowing down what the method could accept.

Imagine a scenario where some code or an API only exposes the base class to you. You call someMethod() to receive a person:

public Person someMethod()
{
    Child child = new Child();
    return child;
}

This is how it would look like at the calling site:

Person receivedPerson = someMethod();
Person myPerson = new Person();
Person result = receivedPerson.getPerson(myPerson); // This will fail

Here, the caller does not know that someMethod() has actually returned a Child instead of a Person. I would think the Person class has a method called getPerson() accepting an object of type Person and therefore, I can call receivedPerson.getPerson(myPerson). But, as the derived type Child which you don't even know about has changed the parameter type of getPerson() to Child, it will not be able to accept myPerson because you cannot convert an object of the base class to an object of a derived class.

This will never happen with covariant return types because if a method returns a more specific type, say Child instead of Person, it can be easily stored in a Person variable and child will always have all the state and behavior as that of its parent.

Upvotes: 2

ernest_k
ernest_k

Reputation: 45339

It's because of the guarantees that the compiler is enforcing to ensure that client code referencing the superclass method is going to be compatible with the subclass.

Because you're allowed to do this:

Person person = new Child();
Person result = person.getPerson();

Nothing is broken when result actually contains a Child object. A Child is a Person.

Now, if you try to apply the same logic to a method parameter, i.e., if you were to assume that it should be okay to call getPerson() with a Child instance, you would run into a problem:

Child child = new Child();

If the compiler were to allow this (as a call to Child.getPerson(Child)):

Person childResult = child.getPerson(child);

The following would be broken:

Person childPerson = child;
Person childResult = childPerson.getPerson(childPerson); //broken

This is broken because you think you're calling the "override", whereas you are not. The compiler is making sure that the contract declared with Person.getPerson() is upheld by all classes that extend person. It's okay to add Child.getPerson(Child), but this would not be overriding Person.getPerson(Person), it would be overloading it. This is because Person.getPerson(Person) should be possible to invoke with any subclass of Person. What if you had a different subclass, Infant, that extended Person directly? How would it work if you called child.getPerson(infant)? The compiler enforces that any kind of Person should be possible to pass to Person.getPerson(Person) or any subclass method overriding it.

In contrast, that is not a problem with the return type. As long as the overriding method returns a Person object, the caller will be able to deal with it, because the caller is prepared to receive a Person object, not any particular subclass.

Upvotes: 3

omar jayed
omar jayed

Reputation: 868

Because the method signature in Java is the name of the method, type, number and order of the parameters. It doesn’t depend on the return type.

For your scenario, getPerson(Person p) method exists in the parent class and, therefore, by rules of inheritance it can be overridden. But the method getPerson(Child p) is not in the parent class. That's why it is not working and throwing an error. The return type of the methods is not playing any role here. Hope that explains your curiosity.

Upvotes: 1

Related Questions