Suppose I need to keep three numbers of Int, Long and Float in a bunch, and access these numbers by their type.
First idea is to use HashMap, where key is type of number, and value is number itself.
val bunch = mapOf<KClass<out Number>, Number>(
Int::class to 0,
Long::class to 0L,
Float::class to 0f,
)
But in this case Kotlin does not inference needed value type, and I need to use explicit cast.
val i: Int = bunch[Int::class]!! // Error: Initializer type mismatch: expected 'Int', actual 'Number'.
val f: Float = bunch[Int::class]!! as Float
Also this does not guarantee that value has the type, spcecified by the key. So I can do
val bunch = mapOf<KClass<out Number>, Number>(
Int::class to 0L, // Long value for Int type
)
val l: Float = bunch[Int::class]!! as Float // Get Int as Float
Can you suggest more appropriate solution for the problem (access value by type with any kind of data structure)?
private class MutableTypedMap private constructor(private val map: MutableMap<Key<*>, Any?>) {
constructor() : this(mutableMapOf())
interface Key<T>
@Suppress("UNCHECKED_CAST")
operator fun <T> get(key: Key<T>): T = map.getValue(key) as T
operator fun <T> set(key: Key<T>, value: T) {
map[key] = value
}
}
You can replace Key<T> with KClass, and everything should work just fine!
Edit: Here you go!
import kotlin.reflect.*
val bunch = MutableTypedMap().apply {
this[Int::class] = 0
this[Long::class] = 0L
this[Float::class] = 0f
}
fun main() {
val i: Int = bunch[Int::class]!!
val f: Float = bunch[Float::class]!!
println(i)
println(f)
}
class MutableTypedMap private constructor(private val map: MutableMap<KClass<*>, Any>) {
constructor() : this(mutableMapOf())
@Suppress("UNCHECKED_CAST")
operator fun <T: Any> get(key: KClass<T>): T? = map[key] as T?
operator fun <T: Any> set(key: KClass<T>, value: T) {
map[key] = value
}
}
You can also check my little library: GitHub - broo2s/typedmap: Type-safe heterogeneous map in Kotlin , although your case is a small subset of its functionality, so it may be considered an overkill. You can also read the README for alternative libraries or approaches.
// create a typed map
val sess = simpleTypedMap()
// add a User item
sess += User("alice")
// get an item of the User type
val user = sess.get<User>()
println("User: $user")
// User(username=alice)
Also look at the Kotlin coroutine code, specifically the CoroutineContext. From what I understand, it’s basically a Map that does exactly what you want; has typed Keys and Values.