Covariant Return Types
How an override can narrow its return type, so callers stop writing casts.
Open this lesson in the learning hubKey points
- An override may return a subtype of what the parent declared. That is a covariant return.
- It is safe because every caller of the parent version still gets something that IS-A the declared type.
- The payoff is at the call site:
Dog puppy = dog.offspring()needs no cast and no generics. - Parameters do not work this way. Change a parameter type and you have written an overload, not an override.
clone()and builder methods are the classic uses: return the concrete type the caller actually holds.
Example
public class Main {
static class Animal {
Animal offspring() { return new Animal(); } // declared return type
@Override public String toString() { return "Animal"; }
}
static class Dog extends Animal {
@Override Dog offspring() { return new Dog(); } // covariant: Dog IS-A Animal
@Override public String toString() { return "Dog"; }
void fetch() { System.out.println("puppy fetches the ball"); }
}
public static void main(String[] args) {
Dog rex = new Dog();
Dog puppy = rex.offspring(); // no cast needed: that is the whole win
puppy.fetch();
Animal seenAsAnimal = rex;
System.out.println("through Animal -> " + seenAsAnimal.offspring());
System.out.println("real class -> " + puppy.getClass().getSimpleName());
}
}
Narrow the return type in the override and every caller gets a cast for free.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.