过滤 Kotlin 对集合,使对内容不为空

Filter Kotlin collection of pairs so pair content is not null

我正在寻找一种将 List<Pair<T?, U?>> 过滤为 List<Pair<T, U>> 的方法:我想忽略包含 null 的对。

有效,但我也需要智能投射:

fun <T, U> List<Pair<T?, U?>>.filterAnyNulls() = 
    filter { it.first != null && it.second != null }

有效,但很丑 !!,而且一点也不符合习惯

fun <T, U> List<Pair<T?, U?>>.filterAnyNulls() = mapNotNull {
    if (it.first == null || it.second == null) {
        null
    } else {
        Pair(it.first!!, it.second!!)
    }
}

有什么好的解决方案吗?还有更通用的解决方案吗? (也许甚至在 类 中也可以是 deconstructed,比如三元组或数据 类?)

您可以先创建一个将 Pair<T?, U?> 转换为 Pair<T, U>? 的函数。我需要将 firstsecond 放入一些变量中以使智能转换工作。您也可以使用 first?.let { t ->

像这样:

fun <T, U> Pair<T?, U?>.toNullablePair() {
    val t = first
    val u = second
    return if (t == null || u == null) null else t to u
}

然后您可以使用它创建您的过滤器函数:

fun <T, U> List<Pair<T?, U?>>.filterAnyNulls() = 
    mapNotNull { it.toNullablePair() }

不幸的是,我认为对此没有好的答案。

如您所说,filter() 选项给出了一个 Pair<T?, U?> 的列表,其中编译器仍然认为该对的值可以为空,即使我们知道它们不能为空。

mapNotNull() 选项解决了这个问题,但需要创建新的 Pair 对象,这是低效的。 (!! 很丑陋,但我认为这是他们被证明是合理的时代之一。遗憾的是编译器的智能转换不够智能,但我知道总类型推断是一个棘手的问题问题,它必须在某个地方停下来。)

我找到了第三个选项,它解决了这两个问题,但代价是不安全的转换:

fun <T, U> List<Pair<T?, U?>>.filterNotNull()
    = mapNotNull{ it.takeIf{ it.first != null && it.second != null } as? Pair<T, U> }

与第二个选项一样,它使用 mapNotNull() 来过滤和转换对;但是,它会进行简单的转换以告诉编译器类型参数不可为空。这 returns a Pair<T, U> 以便编译器知道对值不能为空,并且它避免创建任何新对象(当然除了整个列表)。

但是,它给出了关于不安全转换的编译时警告。它对我来说运行良好 (Kotlin/JVM),但像所有不安全的强制转换一样,不能保证它在所有平台或所有未来版本上都能正常工作。所以并不理想。

这三个选项都有缺点。也许最好的整体是您的第二个选择;代码本身有点难看,而且效率低下(创建新对),但它是安全的,总是有效的,而且很好用。

(顺便说一句,我认为 filterNotNull() 是这个函数更好的名字。在标准库中,过滤器函数似乎是根据它们 保留 ,而不是他们掉落的东西,所以 filterAnyNulls() 会给人完全错误的印象!)

这实际上只是您第二个建议的变体,但另一种选择是显式 return 非空类型:

fun <T, U> List<Pair<T?, U?>>.filterAnyNulls(): List<Pair<T, U>> = this
    .filter { (a, b) -> a != null && b != null }
    .map { (a, b) -> a!! to b!! }

我希望 contracts 可能是避免 !!map 中大喊大叫的解决方案,但看起来它们仍然非常有限,而且还远远不够发达.我认为目前没有办法避免 !!,至少在编译器或合约变得更智能之前是这样。

这个解决方案利用了解构和智能转换,并且会做你需要的而不是丑陋的 !!:

fun <T, U> List<Pair<T?, U?>>.filterPairs(): List<Pair<T, U>> =
    mapNotNull { (t, u) ->
        if (t == null || u == null) null else t to u
    }