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