Search code examples
genericskotlinpolymorphismcovariance

Why Does a Generic Supertype Declaration Not Permit Referencing to Subtype Objects?


I'd like to know the background of why a generic supertype parameter does not allow referencing to subtype objects.

abstract class Pet()

class Cat: Pet()

interface Retailer<T> {
    fun sell(): T
}

class CatRetailer: Retailer<Cat> {
    override fun sell(): Cat {
        println("Sell Cat")
        return Cat()
    }
}

// Type MismatchError prior to compilation  
val steveIrwinTheAnimalEnslaver: Retailer<Pet> = CatRetailer() 

The variable definition results in a type mismatch error where the compiler expects a type of Retailer<Pet>.

However, Pet is a supertype of Cat. Why doesn't polymorphism work like below?

open class SuperClassName() {}

class SubClassName : SuperClassName()

var variableName: SuperClassName = SubClassName()

Solution

  • Pet is a supertype of Cat, but Retailer<Pet> is not a supertype of Retailer<Cat>. To see why imagine you added a method:

    abstract class Pet()
    
    class Cat: Pet()
    class Dog: Pet()
    
    interface Retailer<T> {
        fun sell(): T
        fun buy(x: T): Unit
    }
    
    // only really knows how to buy cats
    val steveIrwinTheAnimalEnslaver: Retailer<Pet> = CatRetailer() 
    
    // legal for any Retailer<Pet>
    steveIrwinTheAnimalEnslaver.buy(Dog())
    

    In this case your options are:

    1. Use Retailer<out Pet>, which disallows calling members like buy which "consume" T. You can think of it as Retailer<any subtype of Pet>, and you won't be much wrong. There is also the dual Retailer<in Pet> which allows calling buy but not sell.

    2. Declare interface Retailer<out T> which disallows declaring buy and means "if A is a subtype of B, then Retailer<A> is a subtype of Retailer<B>.