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.
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.