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