2015-11-07 77 views
3

如果我创建一个数组,然后填充它,科特林认为,有可能是在阵列中的空值,并迫使我考虑到这一点如何告诉Kotlin数组或集合不能包含空值?

val strings = arrayOfNulls<String>(10000) 
strings.fill("hello") 
val upper = strings.map { it!!.toUpperCase() } // requires it!! 
val lower = upper.map { it.toLowerCase() } // doesn't require !! 

创建一个满阵不存在这个问题

val strings = Array(10000, {"string"}) 
val upper = strings.map { it.toUpperCase() } // doesn't require !! 

如何告诉编译器strings.fill("hello")的结果是一个NonNull数组?

+0

我添加了一个更直接解决问题的答案。解释为什么会发生这种情况,以及考虑到上下文(您最终列出了列表,以便为什么不早些拥抱它们)的解决方案,以及如果您无法抗拒的黑客版本来投射阵列。 –

回答

1

经验法则:如果在疑惑,明确指定类型(有针对特殊重构):

val strings1: Array<String?> = arrayOfNulls<String>(10000) 
val strings2: Array<String> = Array(10000, {"string"}) 

所以你看那个strings1包含可为空项,而strings2没有。这一点,只是决定如何使用这些阵列工作:

// You can simply use nullability in you code: 
strings2[0] = strings1[0]?.toUpperCase ?: "KOTLIN" 

//Or you can ALWAYS cast the type, if you are confident: 
val casted = strings1 as Array<String> 

//But to be sure I'd transform the items of the array: 
val asserted = strings1.map{it!!} 
val defaults = strings1.map{it ?: "DEFAULT"} 
+0

我以为我试过'作为Array ',它没有编译,但我看到它。 –

+0

我遇到的问题是'Array(10000,{“string})'运行代码10000次,而不是将值复制1000次 - 我无法相信它是快速的。”map {it !!} ,虽然在这两种情况下,我认为编译器可能非常聪明 –

+0

当我说'as Array IntelliJ警告说演员永远不会成功,如果我没有填写,那是对的,我会得到一个异常。做填充,这是错误的,我没有得到一个例外,但这表明,演员已经检查了每一个元素。所以你所有的建议,我认为涉及迭代数组,以检查我知道的事情是真实的 –

1

有没有办法告诉编译器。变量的类型在声明时确定。在这种情况下,该变量被声明为可包含空值的数组。

fill()方法不声明新变量,它只修改现有的变量的内容,所以它不能导致变量类型改变。

+0

因此没有办法将阵列投射到阵列? –

+0

你总是可以抛出空头,但责任在你身上。 – voddan

1

为什么填充阵列工作正常

填充阵列从拉姆达作为第二通话过程中推断阵列的类型参数:

val strings = Array(10000, {"string"}) 

产生Array<String>

val strings = Array(10000, { it -> if (it % 2 == 0) "string" else null }) 

产生Array<String?>

因此改变声明的=的不拉姆达没有做任何事情来帮助匹配左边。如果发生冲突,则会出现错误。

如何使arrayOfNulls工作

对于arrayOfNulls问题,他们键入您指定的号召arrayOfNulls<String>在函数签名被用作泛型类型T和功能arrayOfNulls返回Array<T?>这意味着空。代码中没有任何内容会更改该类型。 fill方法仅将值设置到现有数组中。

对此可空元素的数组转换为不可为空的元素的列表,使用方法:

val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } 
val strings = nullableStrings.filterNotNull() 
val upper = strings.map { it.toUpperCase() } // no !! needed 

这是很好的,因为你的map调用转换成一个列表,无论如何,那么为什么不事先转换。现在取决于数组的大小,这可能是高性能的,如果在CPU高速缓存中,副本可能会很快。如果是大的,没有高性能,可以让这个懒:

val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } 
val strings = nullableStrings.asSequence().filterNotNull() 
val upper = strings.map { it.toUpperCase() } // no !! needed 

或者,你可以通过做副本留在阵,但实际上这是没有意义的,因为你与map撤消:

val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } 
val strings: Array<String> = Array(nullableStrings.size, { idx -> nullableStrings[idx]!! }) 

在Java或Kotlin代码(JetBrains研究了统计数据)中,数组确实不是很常见,除非代码正在进行真正的低级优化。使用列表可能会更好。

鉴于无论如何你可能会以列表结束,也许从那里开始并放弃阵列。

val nullableStrings = listOf("a","b",null,"c",null,"d") 
val strings = nullableStrings.filterNotNull() 

但是,如果你不能停止追求使用数组,而真的要投一个没有副本...

你总是可以写做两件事情的函数:一是,检查所有值是否为空,如果是,则返回抛出的数组不为null。这有点冒险,但只是因为差异是可以为空才是安全的。

首先,创建于Array<T?>的扩展功能:

fun <T: Any> Array<T?>.asNotNull(): Array<T> { 
    if (this.any { it == null }) { 
     throw IllegalStateException("Cannot cast an array that contains null") 
    } 
    @Suppress("CAST_NEVER_SUCCEEDS") 
    return this as Array<T> 
} 

然后使用此功能的新功能进行转换(元素选中为NOT NULL施法):

val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") } 
val strings = nullableStrings.asNotNull() // magic! 
val upperStrings = strings.map { it.toUpperCase() } // no error 

但我觉得脏甚至在谈论最后的选择。