我有一个抽象类,我们称之为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
}
...
它有一个抽象的映射器。关键是我可以将不同的对象映射到实体并使用read
和write
。
我的孩子课程,我们称之为B,其结构如下:
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")
}
}
}
理想情况下,我想在Mapper
泛型而不是Any
中设置一个界面,但我只是针对这个问题简化了这个界面。
问题是我收到了这个错误:
Type of 'mapper' is not a subtype of the overridden property 'public abstract val mapper: Mapper<Any> defined in ...
为什么?
答案 0 :(得分:6)
请注意有关继承和泛型的两个事实:
只能使用原始属性类型的子类型覆盖val
属性。这是因为该类型的所有用户都希望它返回原始类型的某个实例。例如,可以使用CharSequence
覆盖String
属性。
var
属性甚至不能使用子类型,只能使用原始类型,因为用户可能希望将原始类型的实例分配到属性中。
Kotlin generics are invariant by default。给定Mapper<T>
,如果Mapper<A>
和Mapper<B>
不同,则其任何两个实例化A
和B
都不是彼此的子类型。
鉴于此,您不能使用Mapper<Any>
覆盖Mapper<SomeType>
类型的属性,因为后者不是前者的子类型。
由于Mapper<T>
用作{{1}中的参数类型,因此无法使用declaration-site variance使所有interface Mapper<out T>
用法协变(将接口声明为T
) }}。
您可以尝试应用use-site variance,将该属性声明为
fun toEntity(entry: T): Entity
但是这样,班级abstract val mapper: Mapper<out Any>
的用户又无法拨打A
,因为他们不知道取代fun toEntity(entry: T): Entity
的实际类型是什么子类,因此他们可以安全地通过Any
。但是,如果用户已知确切类型(例如entry
),则会在重写的属性中声明B
的类型。
允许您以更灵活的方式使用重写属性的常见模式是参数化父类mapper
并将属性定义为class A<T>
。
这样,子类型必须指定他们在声明中使用的val mapper: Mapper<T>
:T
,以及看到class B(...) : A<Animal>(...)
的用户(即使实际上并不知道它) A<Animal>
可以安全地将B<Animal>
作为mapper
。