0

I have some Drawers with 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>

Then I have a controller that accept these Drawers, putting them in a ArrayList

private val drawers = ArrayList<BaseGeoDrawer<Any, Any, Any>>()
open fun addGeoDrawer(drawer: BaseGeoDrawer<Any, Any, Any>) {
    drawers.add(drawer)
}

And later on calling methods in these Drawers

//Method in controller
private fun onMarkerClicked(marker: Marker): Boolean {
    return drawers.any { it.onGeoClicked(marker) }
}

//Method in BaseGeoDrawer
fun onGeoClicked(geo: GEO): Boolean

The problem appear on this line

teamAreaDrawer = TeamAreaDrawer(this)
mapController.addGeoDrawer(teamAreaDrawer)

Android Studio will not allow it, telling me

Type mismatch.
Required: BaseGeoDrawer<Any, Any, Any>
Found: TeamAreaDrawer

I tried using out for drawers

private val drawers = ArrayList<BaseGeoDrawer<out Any, out Any, out Any>>()

But then onMarkerClicked will not compile, with the following 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'
David Corsalini
  • 7,958
  • 8
  • 41
  • 66

1 Answers1

1

The problem here is that you need GEO as a contravariant type parameter in BaseGeoDrawer to use onGeoClicked(GEO) but ArrayList<BaseGeoDrawer<Any, Any, Any>> is invariant in its type. This means that you can't add anything else than a BaseGeoDrawer<Any, Any, Any>. If you try to use the types of BaseGeoDrawer as covariant it will not compile because you need it as contravariant when you call onGeoClicked(GEO).

Considering that until now in Kotlin a type parameter can't be bivariant, the only way to do it, is to do an unchecked cast.

In this specific case, you can do:

val teamAreaDrawer = TeamAreaDrawer(this) as BaseGeoDrawer<Any, Any, Any>
mapController.addGeoDrawer(teamAreaDrawer)

If you think about it, in Java, you would have done the same, because you would have had:

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

I recommend you to read more about variance here.

Giorgio Antonioli
  • 15,771
  • 10
  • 45
  • 70