未经检查的转换:尝试在同一方法中将 Int 或 String 转换为 T(通用类型)

Unchecked cast : trying to cast Int or String as T (generic type) in the same method

我对泛型函数还很陌生(在 java 和 kotlin 中)。我使用了一个允许我恢复列表的函数(感谢 SharedPreferences)。这些列表要么是 MutableList<Int><String><Long>,随便什么...这是我当前使用的代码(我使用 list.toString() 保存了列表,只有当它不为空):

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"

所以我的目标是知道如何将 Strings 安全地转换为 T(作为参数传递的列表中元素的类型)。现在我收到警告,想知道我所做的是否正确。我还应该添加一个 in 修饰符吗?例如:list: MutableList<in T> ?

Kotlin 中没有 union types。 所以你不能将类型 T 描述为 IntString,因此你不能将 MutableList<T> 描述为 MutableList<Int>MutableList<String>

但是当你 it.toIntOrNull() ?: it 你得到的甚至不是那个,而是一个可变列表,它可能包含 Int 元素以及 String 元素(因为编译器无法保证该子句将对每个元素以相同的方式解析)。所以编译器试图推断这种类型(它应该是 IntString 的最具体的通用超类型)并且它得到这种可怕的 Comparable<*> & java.io.Serializable 类型。这对 T 可能是什么施加了如此严格的限制,以至于它实际上变得毫无用处(就像使用 MutableList<*>),并且不能用方差注释来修复它。

我建议在这里使用额外的功能参数,将 String(拆分后)转换为所需类型的实例(还要注意,在函数内改变传递的参数是一种代码味道,最好合并在创建它的同一范围内具有现有可变列表):

fun <T> restoreList(sharedPrefsKey: String, converter: (String) -> T): List<T>? =
    savedGame.getString(sharedPrefsKey, null)?.removeSurrounding("[", "]")?.split(", ")?.map { converter(it) }

用法:

val listOfInts = restoreList(sharedPrefKey) { it.toIntOrNull() }
val listOfLongs = restoreList(sharedPrefKey) { it.toLongOrNull() }
val listOfStrings = restoreList(sharedPrefKey) { it }

这是使用具体化泛型执行此操作的方法,当您需要检查泛型类型时,这是必需的。如果您尝试使用 when 子句中不支持的类型调用它,它将抛出错误。

inline fun <reified T: Any> restoreList(sharedPrefsKey: String, list: MutableList<T>) {
    val strings = savedGame.getString(sharedPrefsKey, null)
            ?.removeSurrounding("[", "]")
            ?.split(", ")
    if (strings == null) {
        Log.w("restoreList", "No preferences found for key $sharedPrefsKey.")
        return
    }
    when (T::class) {
        String::class -> strings.mapTo(list) { it as T }
        Int::class -> strings.mapNotNullTo(list) { it.toIntOrNull() as? T }
        Long::class -> strings.mapNotNullTo(list) { it.toLongOrNull() as? T }
        // And so on for other types.
        else -> error("Unsupported type ${T::class}.")
    }
}