I'm pretty new to generic functions (both in java and kotlin). And I use a function that allows me to restore lists (thanks to SharedPreferences
). These lists are either MutableList<Int>
, <String>
, <Long>
, whatever... Here is the code I'm currently using (I saved the list using list.toString()
, only if it wasn't empty) :
fun <T: Any> restoreList(sharedPrefsKey: String, list: MutableList<T>) {
savedGame.getString(sharedPrefsKey, null)?.removeSurrounding("[", "]")?.split(", ")?.forEach { list.add((it.toIntOrNull() ?: it) as T) }
}//"it" is already a String, no need to cast in the "if null" ( ?: ) branch
//warning "Unchecked cast: {Comparable<*> & java.io.Serializable} to T" on "as T"
So my goal is to know how to cast String
s safely to T (the type of the elements inside the list passed as argument). Now I get a warning and want to know if what I'm doing is correct or not. Should I also add an in
modifier? For example : list: MutableList<in T>
?
There is no union types in Kotlin.
So you can't describe a type T
to be either Int
or String
, hence you can't describe a MutableList<T>
to be either MutableList<Int>
or MutableList<String>
But when you do it.toIntOrNull() ?: it
you get even not that, but a mutable list, which may contain Int
elements as well as a String
ones (because compiler have no guarantee that this clause will be resolved same way for each element). So compilers tries to infer this type (which should be a most specific common supertype of both Int
and String
) and it gets this dreadful Comparable<*> & java.io.Serializable
type. This impose so serious restrictions on what T
could be, that it become practically useless (it's like using MutableList<*>
), and it can't be fixed with variance annotations.
I would suggest using additional functional parameter here, converting String
(after splitting) into an instance of required type (also note that mutating passed parameter inside a function is a code smell, it's better to be merged with existing mutable list in the same scope it was created):
fun <T> restoreList(sharedPrefsKey: String, converter: (String) -> T): List<T>? =
savedGame.getString(sharedPrefsKey, null)?.removeSurrounding("[", "]")?.split(", ")?.map { converter(it) }
Usage:
val listOfInts = restoreList(sharedPrefKey) { it.toIntOrNull() }
val listOfLongs = restoreList(sharedPrefKey) { it.toLongOrNull() }
val listOfStrings = restoreList(sharedPrefKey) { it }