将 List<T?> 转换或转换为 List<T>?

Cast or convert List<T?> to List<T>?

我正在寻找一种方便的方法来安全地将 List<T?> 转换为 List<T>?(或者更一般地说,将 Iterable<T?> 转换为 Iterable<T>?)而不过滤任何元素.

换句话说,如果所有元素都不为空,则取列表。

在 Swift 我可以这样做:

let a: [Int?] = [1, nil, 3]
let b: [Int?] = [1, 2, 3]

let aa = a as? [Int] // nil
let bb = b as? [Int] // [1, 2, 3]

在 Kotlin 中,看起来相似的代码只是进行了未经检查的转换,不会导致 aa 成为 null:

val a = listOf<Int?>(1, null, 3)
val b = listOf<Int?>(1, 2, 3)

val aa = a as? List<Int> // [1, null, 3]
val bb = b as? List<Int> // [1, 2, 3]

因此,我正在寻找这样的方法:

@Suppress("UNCHECKED_CAST")
fun <T : Any> Iterable<T?>.takeIfAllNotNull(): Iterable<T>? =
    takeIf { all { el -> el != null } } as? Iterable<T>

有了它,我可以编写如下代码:

val a = listOf<Int?>(1, null, 3)
val b = listOf<Int?>(1, 2, 3)

val aa = a.takeIfAllNotNull() // null
val bb = b.takeIfAllNotNull() // [1, 2, 3]

我一定是漏掉了什么。标准库没有这样的方法吗?

没有内置函数,但您可以结合使用 filterNotNulltakeIf 以获得所需的行为:

val a: List<Int?> = listOf(1, null, 3)
val aa: List<Int>? = a.filterNotNull().takeIf { it.size == a.size }

--

编辑:根据@DrawnRacoon 的建议,仅使用 takeIf:

会更快
val aa: List<Int>? = a.takeIf { null !in a } as List<Int>

这不会创建中间列表,并且会在发现空值时短路。它确实需要不安全的转换来强制编译器更改类型。

在扩展函数中提取它看起来像这样:

@Suppress("UNCHECKED_CAST")
fun <T> Iterable<T?>.takeIfAllNotNull(): Iterable<T>? {
    return takeIf { null !in this } as? Iterable<T>
}

看来我们回到了你最初的提议。所以也许答案应该是:

没有,你没有遗漏任何东西。
标准库中没有针对这种确切行为的方法。


触摸问题中发生的事情:
它与仿制药侵蚀有关。在运行时没有泛型,所以转换只是 ListList 总是成功,它是 returns 原始对象。
编译器会正确地警告您不安全的转换。忽略此警告可能会导致运行时类型不正确:

val a = listOf(1, null, 3)     
val aa = a as? List<Int> // unsafe cast List<Int?> to List<Int> 
// aa is now List<Int> but contains a null

您正在将 List<Int?> 转换为 List<Int>,但运行时无法区分这些类型。
为了安全地转换它,我们实际上必须在某个地方检查列表的内容。最好明确地执行此操作,以便清楚发生了什么,因此在我们的示例中调用了额外的函数。