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 ?
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.
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.
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.
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.
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 ArrayList
s 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 Animal
s.
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")
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With