Victor Manuel Pineda Murcia Victor Manuel Pineda Murcia - 1 month ago 10
Android Question

Kotlin generics inheritance headache

I'm new to Kotlin and I'm trying to compile this code without success (this is only a example of I want to do in a real project):

abstract class Builder<T: Any, Y: Any>

class BuilderImpl() : Builder<String, Int>()

abstract class Shape<T: Any>(){
abstract var builder: Builder<T, *>
}

class Circle() : Shape<String>(){
override var builder: Builder<String, Int> = BuilderImpl()
}


I want to override builder property from Shape class in Circle class. Shape class only knows the first generic type from Builder class. The second one could be any type, so I use * for that.

When I try to override builder property in Circle class, compiler complains with the message:

"Var-property type is 'Builder<String, Int>', which is not a type of overriden public abstract var builder: Builder<String, *>".


I have also try with something like "out Any" instead of *, without success.

I'm don't have any idea about how to get this code compiled.

Answer

When you override a var property, the compiler enforces that

  • getting the value of the overridden property is type-safe: if the original property has type S, the overridden property should return instances of S, that is, its type should be S or its subtype (e.g. it's safe to return an Int when a Number is required);

  • setting the value of the overridden property is type-safe: if the original property has type S, the overridden property should accept objects of S as its value, so its type should be S or some of its supertypes (e.g. if we need to store a String, storing it as Any would be OK)

Given these two requirements, the only type that an overridden var property can have is the original property type itself, because its subtypes and supertypes will violate one of the above conditions. For example, the overridden property should still be able to store a Builder<T, *>, and specifying its type to Builder<T, Int> is not an option:

val s: Shape<SomeType> = Circle<SomeType>()
val b: Builder<SomeType, *> = someBuilder

s.builder = b // should be type-safe, but won't be with the override you want to do

So you simply cannot change the type of a var property when you override it, because, in terms of Kotlin generics, it's in both in and out positions.

You can, however, try one of the following options:

  • Change to a val property. The situation is different when you define a val property: in this case there's no second requirement, and you can change the type of the property to a subtype of the original property type:

    abstract class Shape<T: Any>(){
        abstract val builder: Builder<T, *>
    }
    
    class Circle() : Shape<String>(){
        override var builder: Builder<String, Int> = BuilderImpl() // OK
    }
    
  • Add a generic parameter and specify it in the subclass. In this case, you will be able to use var, because Circle will be a subtype of a specialization Shape<String, Int>, not the original type Shape<T, R>:

    abstract class Shape<T: Any, R: Any>(){
        abstract var builder: Builder<T, R>
    }
    
    class Circle() : Shape<String, Int>(){
        override var builder: Builder<String, Int> = BuilderImpl() // OK
    }
    
Comments