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

Я рекомендую вам прочитать больше о дисперсии здесь.

Другие вопросы по тегам