Kotlin y la confusión de generics
Tengo algunos Drawers
con generics:
abstract class BaseGeoDrawer<KEY : Any, GEO : Any, ITEM : Any> abstract class BasePolygonDrawer<KEY : Any, ITEM : Any>: BaseGeoDrawer<KEY, Polygon, ITEM> class TeamAreaDrawer : BasePolygonDrawer<String, Team> abstract class BaseMarkerDrawer<KEY : Any, ITEM : Any> : BaseGeoDrawer<KEY, Marker, ITEM> class TeamPositionDrawer : BaseMarkerDrawer<String, Team>
Luego tengo un controller
que acepta estos Drawers
, poniéndolos en una ArrayList
- Problema de properties genéricas de Kotlin
- Kotlin "fuera" y "en" y generics - uso correcto
- Función genérica en la interfaz para devolver la implementación concreta
- Kotlin anula la diversión con subtipo
private val drawers = ArrayList<BaseGeoDrawer<Any, Any, Any>>() open fun addGeoDrawer(drawer: BaseGeoDrawer<Any, Any, Any>) { drawers.add(drawer) }
Y más adelante en los methods de llamada en estos Drawers
//Method in controller private fun onMarkerClicked(marker: Marker): Boolean { return drawers.any { it.onGeoClicked(marker) } } //Method in BaseGeoDrawer fun onGeoClicked(geo: GEO): Boolean
El problema aparece en esta línea
teamAreaDrawer = TeamAreaDrawer(this) mapController.addGeoDrawer(teamAreaDrawer)
Android Studio no lo permitirá, diciéndome
Type mismatch. Requinetworking: BaseGeoDrawer<Any, Any, Any> Found: TeamAreaDrawer
Intenté usar drawers
private val drawers = ArrayList<BaseGeoDrawer<out Any, out Any, out Any>>()
Pero entonces onMarkerClicked
no comstackrá, con el siguiente error
Out-projected type BaseGeoDrawer<out Any, out Any, out Any> prohibits the use of 'public final fun onGeoClicked(geo: GEO) defined in mypackage.BaseGeoDrawer'
El problema aquí es que necesita GEO
como un parámetro de tipo contravariante en BaseGeoDrawer
para usar en onGeoClicked(GEO)
pero ArrayList<BaseGeoDrawer<Any, Any, Any>>
es invariante en su tipo. Esto significa que no puede agregar nada más que un BaseGeoDrawer<Any, Any, Any>
. Si intenta utilizar los types de BaseGeoDrawer
como covariante , no se comstackrá porque lo necesita como contravariante cuando llama onGeoClicked(GEO)
.
Teniendo en count que hasta ahora en Kotlin un parámetro de tipo no puede ser bivariante , la única forma de hacerlo es realizar un lanzamiento no verificado.
En este caso específico, puedes hacer:
val teamAreaDrawer = TeamAreaDrawer(this) as BaseGeoDrawer<Any, Any, Any> mapController.addGeoDrawer(teamAreaDrawer)
Si lo piensas bien, en Java, hubieras hecho lo mismo, porque hubieras tenido:
List<BaseGeoDrawer> drawers = new ArrayList<>(); public void example() { TeamAreaDrawer teamAreaDrawer = new TeamAreaDrawer(); drawers.add(teamAreaDrawer); // This is an unchecked cast. drawers.get(0).onGeoClicked("Example"); }
Te recomiendo que leas más sobre la varianza aquí .