Covariant return type & Overriding using covariant type in Java

This tutorial explains about covariant return type in java and how we can override a method using covariant return type. The tutorial explains different aspect of covariant return type along with the advantages of using this.

What is Covariant return type in Java

If type(class) B can be used as a return type while overriding a method whose return type is A, then we call type B as a covariant type of type A. In java this is possible only when type B is a subclass of type A. So we can say all subtypes of a type are covariant type of that type. Covariant return type is applicable for non primitive types only.

Method overriding using covariant return type in Java

If a method in a class returns a non primitive type, then the same method in a subclass of that class can use the same non primitive type or a subclasses of that non primitive type as the return type to override that method.

Let's understand this by an example. In java we have a class Integer which extends a class Number which implicitly extends Object class, so the inheritance structure looks like below.


covariant type in java

If a class A has a method whose return type is an Object type, then the same method in a child class B(child of class A) can use Number or Integer as return type to override that method because Number and Integer class are covariant of Object class. Let's see this by the program given below :

Can we override a method which returns Object type with any non primitive type ?

Yes we can, since Object class is superclass of every class in java.

Java program of method overriding using covariant return type

 class A {
    Object print() {    
      System.out.println("print method of class A");
      return new Object();
    } 
 } 
 class B extends A {   
    Integer print() {
      System.out.println("print method of class B");
      return new Integer(2);
    }    
    public static void main(String [] args) {
      B b = new B();  
      b.print();
      A a = new B();  
      a.print();    
    }
 }

Output:

print method of class B
print method of class B

As you can observe from the output, the print method in class B is overriding the print method of parent class A though the return type of child class method is Integer type It's happening because java allows to use covariant return type while overriding the method.

Let's see one more program to understand it more clearly.

 class A { } 
 class B extends A {  } 
 class C extends A {  }
 
 class D { 
     public A print() {
       System.out.println("print method of class D");
       return new A();
     }   
    public A message() {
      System.out.println("message method of class D");
      return new A();
    }
 } 
 class E extends D {
   public B print() {
      System.out.println("print method of class E");
      return new B();
    }   
   public C message() {
      System.out.println("message method of class E");
      return new C();
    } 
   public static void main(String [] args) {
      E e = new E();  
      e.print();
      D d = new E();  
      d.message();    
    }
 } 

Output:

print method of class E
message method of class E

Here you can see the return type of print and message method of class D is A and we are able to override these methods in class E just by changing the return type as subclasses of class A. It's just because class B and class C are covariant type of class A since both are it's subtypes.

Rules of overriding a method using covariant return type

  • The covariant return type is applicable only for non primitive or object types, not for primitive types.
  • The return type of overriding method in child class can not be a return type which is parent of superclass method's return type. Doing so will result in compilation error.
  • The overriding method's return type in child class should be either the same as the parent class method's return type or a subclass of that type.

Advantages of covariant return type in Java

  • It gives you freedom to return more specific return type while overriding the method which may eliminates unnecessary downcasting from the super type to the subtype. For example clone method of Object class can be overriden by implementing Clonable interface and the implementing class can return the object of its own type which will always be better than returning Object type.
  • It helps us in preventing runtime ClassCastException.