Kotlin и путаница дженериков
У меня есть немного Drawers
с дженериками:
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>
Тогда у меня есть controller
которые принимают эти Drawers
, положив их в ArrayList
private val drawers = ArrayList<BaseGeoDrawer<Any, Any, Any>>()
open fun addGeoDrawer(drawer: BaseGeoDrawer<Any, Any, Any>) {
drawers.add(drawer)
}
И позже при вызове методов в этих Drawers
//Method in controller
private fun onMarkerClicked(marker: Marker): Boolean {
return drawers.any { it.onGeoClicked(marker) }
}
//Method in BaseGeoDrawer
fun onGeoClicked(geo: GEO): Boolean
Проблема появляется в этой строке
teamAreaDrawer = TeamAreaDrawer(this)
mapController.addGeoDrawer(teamAreaDrawer)
Android Studio не допустит этого, рассказывая мне
Type mismatch.
Required: BaseGeoDrawer<Any, Any, Any>
Found: TeamAreaDrawer
Я пытался использовать для drawers
private val drawers = ArrayList<BaseGeoDrawer<out Any, out Any, out Any>>()
Но потом onMarkerClicked
не скомпилируется, со следующей ошибкой
Out-projected type BaseGeoDrawer<out Any, out Any, out Any> prohibits the use of 'public final fun onGeoClicked(geo: GEO) defined in mypackage.BaseGeoDrawer'
1 ответ
Проблема в том, что вам нужно GEO
в качестве контравариантного параметра типа в BaseGeoDrawer
использовать onGeoClicked(GEO)
но ArrayList<BaseGeoDrawer<Any, Any, Any>>
инвариантен в своем типе. Это означает, что вы не можете добавить ничего, кроме BaseGeoDrawer<Any, Any, Any>
, Если вы попытаетесь использовать типы BaseGeoDrawer
как ковариантный, он не будет компилироваться, потому что он вам нужен как контравариантный, когда вы звоните onGeoClicked(GEO)
,
Учитывая, что до сих пор в Kotlin параметр типа не может быть бивариантным, единственный способ сделать это - выполнить непроверенное приведение.
В этом конкретном случае вы можете сделать:
val teamAreaDrawer = TeamAreaDrawer(this) as BaseGeoDrawer<Any, Any, Any>
mapController.addGeoDrawer(teamAreaDrawer)
Если вы подумаете об этом, в Java вы бы сделали то же самое, потому что у вас было бы:
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");
}
Я рекомендую вам прочитать больше о дисперсии здесь.