The “getPreferenceKey” function (below) return type is “Preferences.Key<T>”. However, when I try to assign the return value into “prefKey” of type “Preferences.Key<T>” I get a compile time error saying that the actual return type of “getPreferenceKey” is “Preferences.Key<out T>” for some reason.
How come it’s <out T>? Am I missing something here?
val prefKey: Preferences.Key<T> = getPreferenceKey(key, value::class)
private fun <T : Any> getPreferenceKey(key: String, kClass: KClass<T>): Preferences.Key<T> {
return when (kClass) {
String::class -> stringPreferencesKey(key)
Boolean::class -> booleanPreferencesKey(key)
Int::class -> intPreferencesKey(key)
Long::class -> longPreferencesKey(key)
Float::class -> floatPreferencesKey(key)
Double::class -> doublePreferencesKey(key)
else -> throw IllegalArgumentException("Key-value store does not support the type passed")
} as Preferences.Key<T>
}
To give you additional context: this code is generally not entirely correct from the type safety perspective. It is fine in your specific case, because you support very few types only, but if you write a similar code for other types, it might cause class cast exceptions at runtime. This is why the compiler complains about it.
fun main() {
addToList(Dog(), Cat())
dogs.forEach {} // CCE: Cat cannot be cast to Dog
}
fun addToList(animal1: Animal, animal2: Animal) {
val list = getList(animal1::class)
list += animal1
list += animal2
}
private fun <T : Any> getList(kClass: KClass<out T>): MutableList<T> {
return when (kClass) {
Animal::class -> animals
Dog::class -> dogs
else -> throw IllegalArgumentException()
} as MutableList<T>
}
private val animals = mutableListOf<Animal>()
private val dogs = mutableListOf<Dog>()
open class Animal
class Dog : Animal()
class Cat : Animal()
Let’s ignore why we get a list for the animal1 and then we add animal2 to it. The point is: we added a cat to a list of dogs and compiler allowed us to do this.
The root cause is that animal1::class acquires the runtime type. animal1 is typed as Animal, but animal1::class could return KClass<Animal>, KClass<Dog> or KClass<Cat> - we don’t know, and we can’t pretend it is just KClass<Animal>. This is why out.
Because animal1 is typed as Animal, getList says it returns MutableList<Animal>. In fact, it returns MutableList<Dog>.
As said earlier, this problem doesn’t exist in the specific case of OP, because the list of allowed types is constrained to just a few of them.
edit:
I believe the original code was type-safe. animal1::class returns KClass<out Animal> - meaning we don’t know what’s the exact type. Then getList returns MutableList<out Animal>, so again, we don’t know the exact type of the list, and we can’t add anything to it. After we add out as suggested above, the unchecked cast in as MutableList<T> is no longer type-safe.