Herencia de Kotlin con generics

Tengo una class abstracta, llamémosla 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 } ... 

Tiene un mapeador abstracto. El objective de esto es que puedo asignar diferentes objects a Entity y usar read y write .

Mi class hija, llamémosla B, está estructurada así:

 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") } } } 

Idealmente, quería tener una interfaz en el Mapper genérica y no en Any , pero estoy simplificando esto solo para la pregunta.

El problema es que recibo este error:

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

¿Porqué es eso?

Tenga en count los dos hechos sobre la inheritance y los generics:

  • Una propiedad val solo puede anularse con un subtipo del tipo de propiedad original. Esto se debe a que todos los usuarios del tipo esperan que devuelva alguna instancia del tipo original. Por ejemplo, está bien anular una propiedad CharSequence con String .

    Una propiedad var no puede usar un subtipo, solo el tipo original, ya que los usuarios pueden querer asignar una instancia del tipo original a la propiedad.

  • Los generics de Kotlin son invariables por defecto . Dado Mapper<T> , dos de sus instancias Mapper<A> y Mapper<B> no son subtypes entre sí si A y B son diferentes.

Dado esto, no puede anular una propiedad de tipo Mapper<Any> con Mapper<SomeType> , porque este último no es un subtipo del anterior.

No puede usar la varianza del sitio de statement para hacer que todos Mapper<T> usos de Mapper<T> covariantes (declare la interfaz como interface Mapper<out T> ) debido a que T usa como tipo de parámetro en fun toEntity(entry: T): Entity .

Puede intentar aplicar la varianza del sitio de uso , declarando la propiedad como

 abstract val mapper: Mapper<out Any> 

Pero de esta forma los usuarios de la class A no podrán llamar a fun toEntity(entry: T): Entity , ya que no sabrán cuál es el tipo real que reemplaza a Any en la class child y por lo tanto lo que pueden pasar con security como entry . Sin embargo, si el usuario conoce el tipo exacto (por ejemplo, B ), verá el tipo de mapper tal como se declara en la propiedad anulada.


Un patrón común que puede permitirle usar la propiedad anulada de forma más flexible es parametrizar la class de class A<T> y definir la propiedad como val mapper: Mapper<T> .

De esta forma, los subtypes deberán especificar qué T utilizan en su statement: class B(...) : A<Animal>(...) , y los usuarios que ven A<Animal> (incluso sin saber que es realmente B<Animal> obtendrá su mapper forma segura como Mapper<Animal> .

  • Los methods de logging de Anko no se pueden resolver
  • Buscador de properties o setter esperado en Kotlin
  • ¿Por qué todavía es necesario lanzar el tipo aunque ya se haya asegurado en este ejemplo?
  • Error de la database de Kotlin CursorIndexOutOfBoundsException
  • ¿Cómo puedo cuadrar cada elemento de una matriz de integers en Kotlin?
  • Usando una biblioteca de kotlin en código java
  • Confusión con los generics de Kotlin
  • ¿Cómo convierto un Char a Int?
  • Resaltado de syntax para los files Kotlin-script en Idea
  • Cómo evitar que kotlin.Unit object sea eliminado por Proguard
  • Error de Kotlin al hacer reference a la actividad de la class interna