Covariant Return Type in Java
A Document class has a copy() method that returns a Document. An Invoice extends Document, and when you copy an invoice you get an invoice back, so it would be silly for callers to receive a plain Document and cast it. Since Java 5, an overriding method may declare a more specific return type than the method it overrides. This is called a covariant return type: the return type "varies along with" the class.
A More Specific Return Type
public class Copies {
static class Document {
String title;
Document(String title) { this.title = title; }
Document copy() { return new Document(title); }
}
static class Invoice extends Document {
long amount;
Invoice(String title, long amount) {
super(title);
this.amount = amount;
}
@Override
Invoice copy() { return new Invoice(title, amount); }
}
public static void main(String[] args) {
Invoice original = new Invoice("INV-7 for Srihitha", 4500);
Invoice dup = original.copy();
System.out.println(dup.title + ", Rs. " + dup.amount);
System.out.println(dup != original);
}
}INV-7 for Srihitha, Rs. 4500
trueHow it works
Document.copy()returnsDocument.Invoice.copy()overrides it but returnsInvoice, a subtype ofDocument. That's allowed.original.copy()is called through anInvoicevariable, so the compiler knows it returns anInvoice. No cast is needed to readdup.amount.- The override still honors the parent's promise: an
Invoiceis aDocument, so code that callscopy()through aDocumentvariable still gets aDocument.
Try it yourself: call copy() through a Document doc = original; variable. What type does the compiler say it returns? Can you read .amount without a cast?
The Limits
| Parent returns | Override returns | Allowed? |
|---|---|---|
Document | Invoice (a subclass) | Yes |
Object | String | Yes |
List<String> | ArrayList<String> | Yes |
Invoice | Document (a superclass) | No: that's wider, not narrower |
int | long | No: primitives must match exactly |
void | int | No |
The primitive rule produces this error: return type long is not compatible with int.
Where You See It
clone():Object.clone()returnsObject, but your own class can override it to return its own type, so callers skip the cast (see "Object Cloning in Java").- Builders and fluent APIs: a child builder's methods return the child builder, so chained calls keep the specific type.
StringBuilder.appendreturnsStringBuilder, overriding a method from its parent that returns a more general type.
Common Mistakes
Narrowing a primitive return type. int to long or short isn't covariance; primitives must match.
Widening instead of narrowing. An override can't return a supertype of the parent's return type.
Forgetting the specific type is visible only through the child type. Through a Document variable, copy() still returns Document.
Under the Hood
The JVM identifies a method by its name, parameter types and return type. So how does a call through a Document variable reach Invoice copy()? The compiler adds a hidden bridge method to Invoice:
Copies$Invoice copy();
Code:
0: new #8 // class Copies$Invoice
...
15: areturn
Copies$Document copy();
Code:
0: aload_0
1: invokevirtual #20 // Method copy:()LCopies$Invoice;
4: areturnThe second copy(), returning Document, is the bridge. It overrides the parent's method at the bytecode level and simply calls the real Invoice copy(). You never write it or see it in your source.
Interview Corner
What is a covariant return type?
It's the rule, since Java 5, that an overriding method may return a subtype of the overridden method's return type. For example, Document copy() can be overridden as Invoice copy(). It removes casts for callers using the subclass type. It applies only to reference types; primitive return types must match exactly. The compiler generates a bridge method so the override still matches the parent's method at the bytecode level.
Practice
- Easy: Create
AnimalwithAnimal baby()andDogoverriding it asDog baby(). Call aDog-only method on the result without a cast. - Harder: Override
clone()in aPointclass (implementCloneable) so it returnsPointinstead ofObject, and make itpublic.
Key Takeaways
Was this tutorial helpful?