Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

kotlin generics: Cannot infer type parameter

Tags:

I need a Collection in Kotlin to contain only elements implementing a given interface.

For example: a Map containing Collection of Animals:

interface Animal { val name: String }
data class Monkey(override val name: String): Animal
data class Snake(override val name: String): Animal

From reading the documentation and blogs and SO questions, I wrote this code that uses the Generics in keyword:

class Test {
    private val data = HashMap<String, ArrayList<in Animal>>()        
    init {
        data.put("Monkeys", arrayListOf(Monkey("Kong"), Monkey("Cheetah")))
        data.put("Snakes", arrayListOf(Snake("Monthy"), Snake("Kaa")))
    }        
}

Now I want to add a method in the Test class that reads the content of 'data', for example to print it to the console:

fun printAll() {
   data.forEach { collectionName: String, animals: ArrayList<in Animal> -> 
       println(collectionName)
       animals.forEach { animal: Animal ->
           println("\t$animal")
       }
    }
}

If I do that, I have a compilation error:

Error:(27, 21) Kotlin: Type inference failed: Cannot infer type parameter T in inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit
None of the following substitutions
receiver: Iterable<Any?>  arguments: ((Any?) -> Unit)
receiver: Iterable<Animal>  arguments: ((Animal) -> Unit)
can be applied to
receiver: kotlin.collections.ArrayList<in Animal> /* = java.util.ArrayList<in Animal> */  arguments: ((Animal) -> Unit)

My solution is to force my animal to a an ArrayList<out Animal>:

...
(animals as ArrayList<out Animal>).forEach { animal: Animal ->
    println("\t$animal")
}
...

But I'm not sure that this is the best way to write this kind of code. Is there a better way to tell Kotlin that I want to use sub types in generics for both producers and consumers ?

like image 364
Guillaume Avatar asked May 07 '18 13:05

Guillaume


People also ask

What is type parameter in Kotlin?

The type parameter lets you specify exactly that—instead of “This variable holds a list,” you can say something like “This variable holds a list of strings.” Kotlin's syntax for saying “a list of strings” looks the same as in Java: List<String> . You can also declare multiple type parameters for a class.

How do I find my generic type Kotlin?

There are no direct ways to do this in Kotlin. In order to check the generic type, we need to create an instance of the generic class<T> and then we can compare the same with our class.

What is the difference between * and any in Kotlin generics?

When we define a collection with "*", it should contain the object of only that type. There should not be any mix and match between the data types inside a collection. If we use "Any", we can mix and match the data types, which means we can have multiple data types in a collection.

How do you write generic method Kotlin?

When we want to assign the generic type to any of its super type, then we need to use “out” keyword, and when we want to assign the generic type to any of its sub-type, then we need to use “in” keyword. In the following example, we will use “out” keyword. Similarly, you can try using “in” keyword.


1 Answers

I suppose you don't need the in keyword in the type of data.

Using in here means that you want the type arguments of those ArrayLists to be at least as general as Animal, meaning that an ArrayList<in Animal> may actually be parameterized with a supertype of Animal as well: you could even put an ArrayList<Any> into the map, which makes it clear that it's not type-safe to expect the lists to hold only Animals.

Consider removing the in keyword and leaving just ArrayList<Animal> (or even List<Animal>, which is the interface for a read-only list):

private val data = HashMap<String, List<Animal>>()

init {
    data.put("Monkeys", listOf(Monkey("Kong"), Monkey("Cheetah")))
    data.put("Snakes", listOf(Snake("Monthy"), Snake("Kaa")))
}

fun printAll() {
    data.forEach { collectionName: String, animals: List<Animal> ->
        println(collectionName)
        animals.forEach { animal: Animal ->
            println("\t$animal")
        }
    }
}
like image 150
hotkey Avatar answered Oct 04 '22 16:10

hotkey