Kotlin遗传与泛型

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)

它有一个抽象的映射器.关键是我可以将不同的对象映射到实体并使用readwrite.

我的孩子班,我们称之为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 ...

这是为什么?

hot*_*key 6

请注意有关继承和泛型的两个事实:

  • val属性只能与原始属性类型的子类型覆盖.那是因为该类型的所有用户都希望它返回原始类型的一些实例.例如,可以使用覆盖CharSequence属性String.

    一个var属性甚至不能用一个亚型,只有原来的类型,因为用户可能希望将原始类型的实例分配到属性.

  • Kotlin泛型默认是不变的.给定Mapper<T>,它的任何两个实例,Mapper<A>并且Mapper<B>如果AB不同则不是彼此的子类型.

鉴于此,不能覆盖类型的属性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>.