Type mismatch with List of class with generics

Viewed 145

This is my code:

interface ClaseA<I: ClaseB, V: ClaseC> {
    fun otherMethod(item: I, holder: V)
}

interface ClaseB {
    fun getViewType(): String
}

abstract class ClaseC {
    abstract fun someMethod()
}

class HijoB: ClaseB {
    override fun getViewType() = "Prueba"
}

class HijoC: ClaseC() {
    override fun someMethod() {
        print("Some method, hijoC")
    }
}

class HijoA: ClaseA<HijoB, HijoC> {
    override fun otherMethod(item: HijoB, holder: HijoC) {
        println(item.getViewType())
        println(holder.someMethod())
    }
}

fun main() {
    val list: MutableList<ClaseA<out ClaseB, out ClaseC>> = mutableListOf()
    val hijoA: ClaseA<HijoB, HijoC> = HijoA()
    val hijoB: HijoB = HijoB()
    val hijoC: HijoC = HijoC()
    
    list.add(hijoA)
    
    list.forEach { item -> item.otherMethod(hijoB, hijoC) }
    
    println("Hello, world!!!")
}

I put an example code in a Kotlin Playground:

https://pl.kotl.in/BU0J-FkfR

I dont want to cast the item of the list.

The error i have when i press play:

Type mismatch: inferred type is HijoB but Nothing was expected Type mismatch: inferred type is HijoC but Nothing was expected

2 Answers

You have an issue with your generic variance here.

The beginning of your code may look OK:

  • HijoA is-a ClaseA<HijoB, HijoC> by definition of the HijoA
  • ClaseA<HijoB, HijoC> is a subtype of ClaseA<out ClaseB, out ClaseC> by definition of the out variance

However, the problem is that there is nothing you can do with a ClaseA<out ClaseB, out ClaseC> as far as the method otherMethod is concerned.

Let me clarify:

  • out ClaseB means "some subtype of ClaseB"
  • out ClaseC means "some subtype of ClaseC"
  • the method otherMethod takes I and V as arguments, so in the case of ClaseA<out ClaseB, out ClaseC>, it takes "some subtype of ClaseB" and "some subtype of ClaseC" as arguments, but we don't know which ones (when seeing this type you don't know what the actual instance accepts exactly)
  • any method can accept subtypes of its parameters (that's a general rule of inheritance)

Given the above, ClaseA<out ClaseB, out ClaseC> give us so little information that the only arguments that are guaranteed to be acceptable for the method otherMethod are:

  • first argument: the subtypes of every possible subtype of ClaseB
  • second argument: the subtypes of every possible subtype of ClaseC

Conclusion: the only generally acceptable type for both arguments is the type at the bottom of the hierarchy, which is the subtype of every type: Nothing. This essentially means you cannot call the method otherMethod in any meaningful way.

Solution

You have to be more specific on the element type of that list for these elements to by useful:

val list: MutableList<ClaseA<HijoB, HijoC>> = mutableListOf()

Or

val list: MutableList<HijoA> = mutableListOf()

Note that you ended up with this problem because you are trying to declare a variance that's the opposite of what the ClaseA's variance actually should be. It only takes I and V as inputs (method arguments), so you could declare your interface like this:

interface ClaseA<in I: ClaseB, in V: ClaseC> {
    fun otherMethod(item: I, holder: V)
}

This would have highlighted early on that there was a problem with the declaration of list.

However, if you have other methods in ClaseA that return values of type I or V, then you won't be able to use this declaration-site variance. You can stick with your existing declaration of ClaseA then. But you still have to fix the declaration of list.

Your list is typed incorrectly.

Your list should be:

val list: MutableList<HijoA> = mutableListOf()

OR

val list: MutableList<ClaseA<HijoB, HijoC>> = mutableListOf()

Otherwise you are not compliant with the interface you have defined. By defining the MutableList as:

val list: MutableList<ClaseA<out ClaseB, out ClaseC>> = mutableListOf()

It is not actually a super-type of the HijoA class, because the out limits the contract more than the definition of the HijoA class signature does.

You cannot use out in this case, because you are receiving the generics as function parameters. If you never return the values, you could call them in but then you are again limiting the contract by only allowing downstream implementations, via contravariance.

Related