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

fph*_*ipe 1 iterable casting kotlin

我正在寻找一种方便的方法来安全地将 a List<T?>to List<T>?(或更一般地说,Iterable<T?>to 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]
Run Code Online (Sandbox Code Playgroud)

在科特林中,同样看代码只是做一个未经检查的演员和工作不会导致aanull

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]
Run Code Online (Sandbox Code Playgroud)

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

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

有了这个,我可以写我的代码如下:

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]
Run Code Online (Sandbox Code Playgroud)

我一定在这里遗漏了一些东西。标准库没有这样的方法吗?

Rob*_*bCo 7

没有内置功能,但您可以组合filterNotNulltakeIf获得所需的行为:

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

——

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

val aa: List<Int>? = a.takeIf { null !in a } as List<Int>
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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


触及问题中发生的事情:
它与泛型侵蚀有关。在运行时没有泛型所以剧组只是ListList它总是会成功,并返回原来的对象。
编译器会正确警告您有关不安全转换的信息。忽略此警告可能会导致运行时类型不正确:

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
Run Code Online (Sandbox Code Playgroud)

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

  • 你不需要过滤它,只需 `val aa = a.takeIf { null !in it }` (3认同)