It’s legal to substitute a child class as the return type of a base class when overriding a function. Doing so allows us to avoid unnecessary casting. Let’s look at an example.
abstract class Cook(val name : String) { //Abstract method that returns Cook abstract fun copy() : Cook } class Bob(name : String) : Cook(name) { /** * Notice how this method changes the * return type from Cook to Bob. This is legal * because Bob is a child class of Cook. * * This is called covariant return types */ override fun copy(): Bob { return Bob(this.name) } }
In this example, we have a base class Cook that has an abstract copy() method. The copy() method returns a Cook object. However, Bob overrides copy() and returns Bob. The difference may look small, but when used we do not need to downcast Cook objects back to Bob.
fun main(args : Array<String>){ val bob = Bob("Bob") //No casting required since copy() returns a Bob val bobCopy : Bob = bob.copy() val cookCopy : Cook = bob.copy() println(bob.name) println(bobCopy.name) println(cookCopy.name) }
If we had the copy() method in Bob return Cook rather than Bob, we would need to down cast.
class Bob(name : String) : Cook(name) { //This verion returns the Base Type override fun copy(): Cook { return Bob(this.name) } } val bobCopy : Bob = bob.copy() as Bob //Now a cast is required
So although we could have Bob.copy() return Cook, we now have to immediately downcast the result back into a Bob object. This doesn’t really make a lot of sense for a copy() method, which is why it’s acceptable to override a method by returning a child class type when needed.
Putting it Together
Here is an example program that shows off covariant return types.
package ch1.overriding abstract class Cook(val name : String) { //Abstract method that returns Cook abstract fun copy() : Cook } class Bob(name : String) : Cook(name) { /** * Notice how this method changes the * return type from Cook to Bob. This is legal * because Bob is a child class of Cook. * * This is called covariant return types */ override fun copy(): Bob { return Bob(this.name) } } fun main(args : Array<String>){ val bob = Bob("Bob") //No casting required since copy() returns a Bob val bobCopy : Bob = bob.copy() val cookCopy : Cook = bob.copy() println(bob.name) println(bobCopy.name) println(cookCopy.name) }
Here is the output when run
Bob Bob Bob