具有非空值的 Kotlin 映射

问题描述 投票:0回答:6

假设我有一个 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 都是不可变的时,为什么不能在编译时解决这个问题?

collections kotlin
6个回答
32
投票

还有另一种方法可以从地图中获取非空值

fun <K, V> Map<K, V>.getValue(key: K): V

抛出 NoSuchElementException - 当映射不包含指定键的值并且没有为该映射提供隐式默认值时。

but 运算符 get == map[] 返回可为空的

operator fun <K, V> Map<out K, V>.get(key: K): V?

18
投票

这与 Map 的实现无关(无论是基于 Kotlin 还是基于 Java)。您正在使用映射,并且映射可能没有键,因此 [] 运算符返回可为空类型。


12
投票

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))

0
投票

简单的回答是,除非 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


0
投票

我找到了一个不错的解决方案:

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"]

0
投票

这不是在当前编译器编译时确定的,逻辑是任何消费者只能通过

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]!! } }
    
© www.soinside.com 2019 - 2024. All rights reserved.