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