Covariant Return Types

OOP · lesson 22 of 43 · 2 min read

How an override can narrow its return type, so callers stop writing casts.

Open this lesson in the learning hub

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