假设我有一个 Map 用于将扑克牌的字母转换为整数
val rank = mapOf("J" to 11, "Q" to 12, "K" to 13, "A" to 14)
在使用地图时,即使 Map 和 Pair 是不可变的,我似乎总是必须进行空安全检查:
val difference = rank["Q"]!! - rank["K"]!!
我猜这是因为泛型类型有 Any?超类型。当 Map 和 Pair 都是不可变的时,为什么不能在编译时解决这个问题?
这与 Map 的实现无关(无论是基于 Kotlin 还是基于 Java)。您正在使用映射,并且映射可能没有键,因此 [] 运算符返回可为空类型。
mapOf() 提供了一个 Map,但不保证键的存在——这是预期的,特别是考虑到 Map 的 Java 实现。
虽然我个人可能更喜欢坚持使用空安全调用和 elvis 运算符,但听起来您更喜欢在调用站点使用更清晰的代码(特别是考虑到您知道这些键存在并且具有关联的非空值)。考虑一下:
class NonNullMap<K, V>(private val map: Map<K, V>) : Map<K, V> by map {
override operator fun get(key: K): V {
return map[key]!! // Force an NPE if the key doesn't exist
}
}
通过委托给map的实现,但重写get方法,我们可以保证返回值非空。这意味着您不再需要担心 !!、?. 或 ?: 对于您的用例。
一些简单的测试代码表明这是真的:
fun main(args: Array<String>) {
val rank = nonNullMapOf("J" to 11, "Q" to 12, "K" to 13, "A" to 14)
val jackValue: Int = rank["J"] // Works as expected
println(jackValue)
val paladinValue: Int = rank["P"] // Throws an NPE if it's not found, but chained calls are considered "safe"
println(jackValue)
}
// Provides the same interface for creating a NonNullMap as mapOf() does for Map
fun <K, V> nonNullMapOf(vararg pairs: Pair<K, V>) = NonNullMap(mapOf<K, V>(*pairs))
简单的回答是,除非 Kotlin 发生改变,否则你无法实现这一目标。正如其他人指出的那样,这与可变性无关,而是与 Java 的 Maps 接受 null 作为有效值这一事实有关。目前,Kotlin 的
*Map
类与 Java 的 *Map
类具有完全相同的实现。
如果您仍然想实现非空值映射,您需要实现自己的,例如延伸
Map
或环绕它
更具体地说,在幕后,
mapOf
为我们提供了一个Kotlin的LinkedHashMap,它不是一个不同的类,而只是Java的LinkedHashMap的一个typealias
地图.kt
public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> =
if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap()
类型别名.kt
@SinceKotlin("1.1") public actual typealias LinkedHashMap<K, V> = java.util.LinkedHashMap<K, V>
你可以尝试用
map.getValue(key)
代替map.get(key)
,但我个人认为这不干净且令人困惑。
也许 Dan Lew 这里的其他一些内容对您有用?
我的 Kotlin 版本是
1.3.72-release-IJ2020.1-3
我找到了一个不错的解决方案:
val rank = object {
val rankMap = mapOf("J" to 11, "Q" to 12, "K" to 13, "A" to 14)
operator fun get(key: String): Int = rankMap[key]!!
}
val difference = rank["Q"] - rank["K"]
这不是在当前编译器编译时确定的,逻辑是任何消费者只能通过
Map
标签看到它,因此可能会以意想不到的方式使用它......即使这绝对考虑到 TypeScript 具有此功能,它可以成为编译器的一部分。 不过,考虑到您可能拥有的不同映射和列表子类型的数量,他们可能发现最好不要对集合类型进行任何特殊处理。
无论如何,在其他情况下,您绝对确定实际上可以永远从映射接收空值,例如如果提供了默认值或当您覆盖了枚举的所有可能值时你的钥匙。
在这种情况下,将其包装在值类中可以让您在不进行空检查的情况下检索值:
@JvmInline
value class NonNullMap<T : Any, U : Any>(val original: Map<T, U>): Map<T, U> by original {
override operator fun get(key: T): U {
return internal[key]!!
}
}