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 } ... 

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

这是为什么?

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

  • val属性只能被原始属性类型的子类型覆盖。 这是因为所有类型的用户期望它返回原始类型的一些实例。 例如,可以用String重写CharSequence属性。

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

  • Kotlin泛型默认是不变的 。 鉴于Mapper<T> ,如果AB不同,任何两个实例化Mapper<A>Mapper<B>都不是彼此的子类型。

鉴于此,您不能用Mapper<SomeType>覆盖Mapper<Any>类型的属性,因为后者不是前者的子类型。

由于T用作fun toEntity(entry: T): Entity中的参数类型,因此不能使用声明站点差异来使所有Mapper<T>用法协变(将接口声明为interface Mapper<out T> )。

您可以尝试应用使用地点差异 ,声明属性为

 abstract val mapper: Mapper<out Any> 

但是这样,类A的用户将无法调用fun toEntity(entry: T): Entity ,因为他们不知道在子类中替换Any的实际类型是什么,因此他们可以安全地通过作为entry 。 但是,如果用户知道确切的类型(例如B ),他们将看到在覆盖的属性中声明的mapper的类型。


一个可以允许你以更灵活的方式使用重写属性的通用模式是参数化父类class A<T>并将属性定义为val mapper: Mapper<T>

这样,子类型必须指定他们在声明中使用哪个Tclass B(...) : A<Animal>(...) ,以及看到A<Animal>的用户(甚至不知道它是实际的B<Animal>将安全地将其mapperMapper<Animal>