Covariant Return Type in Java


Covariant return type in an Object oriented programming language means that the return type of any method can be replaced by a narrower type when it is overridden in the subclass or child class. We have explored it in Java in depth.

Covariant return type is based on the Liskov substitution principle. This is a principle in Object oriented programming which states that, in a program, if X is a subtype of Y, then the objects of type Y may be replaced with objects of type X that means an object of type Y may be substituted with any object of a subtype X without changing any of the desirable properties of the program such as task performed, correctness, etc.

Covariant return type in java was not supported until Java version 5 that means it was not possible to override any method in the subclass by changing it's return type in the earlier versions of java.

As we already know that we can override a method in Java by defining the method in the child class or derived class by the same name as in the parent class or base class with same number of parameters and also the return type must be same in case of primitive data type such as int or float. In this case the overriding method is said to be "invariant" with respect to the return type.

One point to note is that changing the parameters of the method is known as method overloading not overriding.

Overriding a method in subclass by changing the return type works only if the return type of the overriding method is a subtype of the base class's return type.

Now lets see how can we override a method by changing it's return type.

Example:

class Shape{
    public Shape getShape(){
        Shape s = new Shape();
        //some code
        return s;
    }
}

class Rectangle extends Shape{
    @Override
    public Rectangle getShape(){
        Rectangle s = new Rectangle();
        //some code
        return s;
    }
}

In the above example we have a base class named Shape which has a method named "getShape()" that returns an object of type Shape so this methods return type is Shape type. Now the second class Rectangle inherits form the Shape class and overrides it's "getShape()" method and return the object of type Rectangle class. So this particular example we have demonstrated covariant return type. See the following program as it will make the concept more clear.

@Override annotation:

@Override annotation is a line that indicates that we are overriding a method of the parent class and it is considered a best practice to use it, although there won't be any error if we miss it out. Another aspect of this annotation is that improves the readability of the source code.

Program

//This program demonstrates covariant return type in java
class A {
    A getObj() {
        System.out.println("Base class method.");
        return new A(); //Returning a new object of type A.
    }
}

class B extends A {
    //Overriding getObj method.
    @Override
    B getObj() {
        System.out.println("Derived class method.");
        return new B(); //Returning a new object of type B.
    }
}

public class Main {
    public static void main(String[] args) {
        B b = new B(); //Creating an object of derived class B.
        b.getObj(); //invoking the getObj method of class B.
    }
}

Output:

Derived class method.

In the above program we have created a class named A which has a method getObj() that outputs a message "Base class method." and returns an object of it's own class. The second class is named as B which inherits from class A and overrides the getObj() method by changing the return type of the method.

One important thing to note is that covariant return type is possible only with non-primitive return types which are nothing but classes.

Advantages

  • It improves readability of the code better.
  • It makes the maintenance of the program easier.
  • The return types are more specific and it helps in avoiding unnecessary type casts.
  • It is used to prevent ClassCastExceptions which is thrown to indicate that the code has attempted to cast an object to a subclass of which it is not an instance.

Return Types

The return type of child class method which is overriding a method can vary form the return type of parent class method. The idea of return-type-substitutability allows the covariance of return type.

For example if a method named m1 with the return type R1 is said to be return-type-substitutable with any other method name m2 and with return type R2, if and only if the conditions below are met:

  • If R1 is void then R2 is also void.
  • If R1 is primitive, then R2 is identical to R1.
  • Else if it is a reference type then:
    • Either R1 is a subtype of R2 or R1 can be converted to a subtype of R2 by unchecked conversion, or
    • R1 = |R2|

|R2| refers to *erasure of R2.

How is this implemented?

The java programming language does not actually allow the overloading of methods based on the return type but JVM always allowed overloading based on the return type. JVM allows this as it uses full signature of a method for it's lookup or resolution. Full signature of a method includes the parameters along with the return type of the method. This means that a class can have two or more methods which differ only by their return type. The java compiler (javac) uses this fact to support and implement covariant return types.

From the above example of Shape and Rectangle classes.
For the Rectangle class the javac compiler generated the following code.

Disassembled Rectangle class code

class Rectangle extends Shape {
  Rectangle();
  public Rectangle getShape();
  public Shape getShape();
}

You can use javap which is a java class file disassembler to verify the above code. It must be noted that we still cannot use the covariant return type in java but javac makes it possible by using this approach. So due to this fact there needs to be no change required in the JVM to support covariant return type.

With this article at OpenGenus, you must have the complete idea of Covariant Return Type in Java. Enjoy.