pav*_*163 2 java generics inheritance kotlin
我有一个抽象类,我们称之为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
}
...
Run Code Online (Sandbox Code Playgroud)
它有一个抽象的映射器.关键是我可以将不同的对象映射到实体并使用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")
}
}
}
Run Code Online (Sandbox Code Playgroud)
理想情况下,我希望在Mapper通用中有一个接口而不是Any,但我只是为了这个问题简化了这个.
问题是我收到此错误:
Type of 'mapper' is not a subtype of the overridden property 'public abstract val mapper: Mapper<Any> defined in ...
这是为什么?
请注意有关继承和泛型的两个事实:
甲val属性只能与原始属性类型的子类型覆盖.那是因为该类型的所有用户都希望它返回原始类型的一些实例.例如,可以使用覆盖CharSequence属性String.
一个var属性甚至不能用一个亚型,只有原来的类型,因为用户可能希望将原始类型的实例分配到属性.
Kotlin泛型默认是不变的.给定Mapper<T>,它的任何两个实例,Mapper<A>并且Mapper<B>如果A和B不同则不是彼此的子类型.
鉴于此,不能覆盖类型的属性Mapper<Any>有Mapper<SomeType>,因为后者不是前者的一个亚型.
您不能使用声明站点方差来使所有Mapper<T>用法协变(将接口声明为interface Mapper<out T>),因为它T被用作参数的类型fun toEntity(entry: T): Entity.
您可以尝试应用使用场地差异,将该属性声明为
abstract val mapper: Mapper<out Any>
Run Code Online (Sandbox Code Playgroud)
但是这样,类的用户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作为Mapper<Animal>.