从 Kotlin 中的映射中过滤空键和值
Filter null keys and values from map in Kotlin
我有一个扩展功能,可以过滤掉具有空键或空值的条目:
fun <K, V> Map<K?, V?>.filterNotNull(): Map<K, V> = this.mapNotNull {
it.key?.let { key ->
it.value?.let { value ->
key to value
}
}
}.toMap()
这适用于具有可为空键和值的映射:
data class Stuff(val foo: Int)
val nullMap = mapOf<String?, Stuff?>(null to (Stuff(1)), "b" to null, "c" to Stuff(3))
assert(nullMap.filterNotNull().map { it.value.foo } == listOf(3))
但在具有不可为 null 的键或值的键或值中则不然:
val nullValues = mapOf<String, Stuff?>("a" to null, "b" to Stuff(3))
assert(nullValues.filterNotNull().map { it.value.foo } == listOf(3))
Type mismatch: inferred type is Map<String, Stuff?> but Map<String?, Stuff?> was expected
Type inference failed. Please try to specify type arguments explicitly.
有没有办法让我的扩展函数适用于这两种情况,或者我是否需要提供两个单独的函数?
使用时可以指定地图类型mapOf
assert(mapOf<String?, String?>("x" to null, "a" to "b").filterNotNull() == mapOf("a" to "b"))
编辑
您为 Map<K?, V?>
指定了扩展函数,并试图使用它作为推断 Map<String, String>
(在您的原始问题中),因此它不会工作,因为 Map<String, String>
不是Map<K?, V?>
的子类型,因为映射接口定义为 Map<K, out V>
。它在键参数类型上不变,在值参数类型上协变。
您可以做的是通过将 Map<K?, V?>
更改为 Map<out K?, V?>
来使扩展函数中的键类型也具有协变性。现在,Map<String, String>
或 Map<String, String?>
将成为 Map<K? V?>
的子类型。
您也可以使用一个 biLet 代替两个嵌套的 let:How can I check 2 conditions using let (or apply etc)
稍后我会找出原因,但添加到地图上是可行的:
fun <K : Any, V : Any> Map<out K?, V?>.filterNotNull(): Map<K, V> = ...
解决方案
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> = this.mapNotNull {
it.key?.let { key ->
it.value?.let { value ->
key to value
}
}
}.toMap()
我觉得太复杂了。可以写成
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> =
filter { it.key != null && it.value != null } as Map<K, V>
丑陋的转换是必要的,因为编译器(还)不能推断出键和值都不包含 null。
警告,关于协方差out K?
是的,它提供了不仅对Map<String?, Stuff?>
而且对Map<String, Stuff?>
(键是否可为空)使用相同方法的可能性。但是这种自由是有代价的。对于已知没有空键的地图,您不必为每个条目支付空值比较。
在您的初始解决方案中(即 K 上没有协方差),编译器可能会阻止您调用该低效方法。那么正确的方法可能是
fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>
我有一个扩展功能,可以过滤掉具有空键或空值的条目:
fun <K, V> Map<K?, V?>.filterNotNull(): Map<K, V> = this.mapNotNull {
it.key?.let { key ->
it.value?.let { value ->
key to value
}
}
}.toMap()
这适用于具有可为空键和值的映射:
data class Stuff(val foo: Int)
val nullMap = mapOf<String?, Stuff?>(null to (Stuff(1)), "b" to null, "c" to Stuff(3))
assert(nullMap.filterNotNull().map { it.value.foo } == listOf(3))
但在具有不可为 null 的键或值的键或值中则不然:
val nullValues = mapOf<String, Stuff?>("a" to null, "b" to Stuff(3))
assert(nullValues.filterNotNull().map { it.value.foo } == listOf(3))
Type mismatch: inferred type is Map<String, Stuff?> but Map<String?, Stuff?> was expected
Type inference failed. Please try to specify type arguments explicitly.
有没有办法让我的扩展函数适用于这两种情况,或者我是否需要提供两个单独的函数?
使用时可以指定地图类型mapOf
assert(mapOf<String?, String?>("x" to null, "a" to "b").filterNotNull() == mapOf("a" to "b"))
编辑
您为 Map<K?, V?>
指定了扩展函数,并试图使用它作为推断 Map<String, String>
(在您的原始问题中),因此它不会工作,因为 Map<String, String>
不是Map<K?, V?>
的子类型,因为映射接口定义为 Map<K, out V>
。它在键参数类型上不变,在值参数类型上协变。
您可以做的是通过将 Map<K?, V?>
更改为 Map<out K?, V?>
来使扩展函数中的键类型也具有协变性。现在,Map<String, String>
或 Map<String, String?>
将成为 Map<K? V?>
的子类型。
您也可以使用一个 biLet 代替两个嵌套的 let:How can I check 2 conditions using let (or apply etc)
稍后我会找出原因,但添加到地图上是可行的:
fun <K : Any, V : Any> Map<out K?, V?>.filterNotNull(): Map<K, V> = ...
解决方案
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> = this.mapNotNull {
it.key?.let { key ->
it.value?.let { value ->
key to value
}
}
}.toMap()
我觉得太复杂了。可以写成
fun <K, V> Map<out K?, V?>.filterNotNull(): Map<K, V> =
filter { it.key != null && it.value != null } as Map<K, V>
丑陋的转换是必要的,因为编译器(还)不能推断出键和值都不包含 null。
警告,关于协方差out K?
是的,它提供了不仅对Map<String?, Stuff?>
而且对Map<String, Stuff?>
(键是否可为空)使用相同方法的可能性。但是这种自由是有代价的。对于已知没有空键的地图,您不必为每个条目支付空值比较。
在您的初始解决方案中(即 K 上没有协方差),编译器可能会阻止您调用该低效方法。那么正确的方法可能是
fun <K, V> Map<K, V?>.filterValuesNotNull() = filterValues { it != null } as Map<K, V>