I have an abstract class, let's call it A.
abstract class A(private val name: String) {
fun read(key: String): Entity {
...
}
fun write(entity: Entity) {
...
}
abstract val mapper: Mapper<Any>
...
interface Mapper<T> {
fun toEntity(entry: T): Entity
fun fromEntity(entity: Entity): T
}
...
It has an abstract mapper. The point of that is that I can map different objects to Entity and use read and write.
My child class, let's call it B, is structured like this:
class B(private val name: String) : A(name) {
override val mapper = AnimalMapper
object AnimalMapper: Mapper<Animal> {
override fun fromEntity(entity: Entity): Animal {
TODO("not implemented")
}
override fun toEntity(animal: Animal): Entity {
TODO("not implemented")
}
}
}
Ideally, I wanted to have an interface in the Mapper generic and not Any, but I'm simplifying this just for the question.
The issue is that I get this error:
Type of 'mapper' is not a subtype of the overridden property 'public abstract val mapper: Mapper<Any> defined in ...
Why is that?
Note the two facts about inheritance and generics:
A val property can only be overridden with a subtype of the original property type. That's because all the users of the type expect it to return some instance of the original type. For example, it's okay to override a CharSequence property with String.
A var property cannot even use a subtype, only the original type, because the users may want to assign an instance of the original type into the property.
Kotlin generics are invariant by default. Given Mapper<T>, any two of its instantiations Mapper<A> and Mapper<B> are not subtypes of each other if A and B are different.
Given this, you cannot override a property of type Mapper<Any> with Mapper<SomeType>, because the latter is not a subtype of the former.
You cannot use declaration-site variance to make all Mapper<T> usages covariant (declare the interface as interface Mapper<out T>) because of T used as the type of a parameter in fun toEntity(entry: T): Entity.
You can try to apply use-site variance, declaring the property as
abstract val mapper: Mapper<out Any>
But this way the users of the class A won't be able to call fun toEntity(entry: T): Entity, since they will not know what is the actual type that replaces Any in the child class and thus what they can safely pass as entry. However, if the exact type (e.g. B) is known to the user, they will see the type of mapper as it is declared in the overridden property.
A common pattern that can allow you to use the overridden property in a more flexible way is to parameterize the parent class class A<T> and define the property as val mapper: Mapper<T>.
This way, the subtypes will have to specify which T they use in their declaration: class B(...) : A<Animal>(...), and the users that see A<Animal> (even without knowing it's actually B<Animal> will safely get its mapper as Mapper<Animal>.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With