Kotlin遗传与泛型

时间:2017-09-19 16:28:08

标签: 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
    }

    ...

它有一个抽象的映射器。关键是我可以将不同的对象映射到实体并使用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")
        }
    }
}

理想情况下,我想在Mapper泛型而不是Any中设置一个界面,但我只是针对这个问题简化了这个界面。

问题是我收到了这个错误:

Type of 'mapper' is not a subtype of the overridden property 'public abstract val mapper: Mapper<Any> defined in ...

为什么?

1 个答案:

答案 0 :(得分:6)

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

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

    var属性甚至不能使用子类型,只能使用原始类型,因为用户可能希望将原始类型的实例分配到属性中。

    < / LI>
  • Kotlin generics are invariant by default。给定Mapper<T>,如果Mapper<A>Mapper<B>不同,则其任何两个实例化AB都不是彼此的子类型。

鉴于此,您不能使用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