我如何告诉 Kotlin 数组或集合不能包含空值?
How can I tell Kotlin that an array or collection cannot contain nulls?
如果我创建一个数组,然后填充它,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 数组?
无法将此告知编译器。变量的类型在声明时就确定了。在这种情况下,变量被声明为可以包含空值的数组。
fill()方法不声明新变量,只是修改已有变量的内容,所以不会引起变量类型的改变。
一条经验法则:如果有疑问,请明确指定类型(对此有一个特殊的重构):
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"}
为什么填充数组工作正常
填充数组在调用期间从用作第二个参数的 lambda 推断数组的类型:
val strings = Array(10000, {"string"})
产生Array<String>
val strings = Array(10000, { it -> if (it % 2 == 0) "string" else null })
产生Array<String?>
因此,更改 =
左侧与 lambda 不匹配的声明没有任何帮助。有冲突就报错
如何使 arrayOfNulls 工作
对于 arrayOfNulls
问题,他们将您指定给调用的类型 arrayOfNulls<String>
在函数签名中用作泛型类型 T
和函数 arrayOfNulls
returns 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()
但是,如果你不能停止使用数组的任务,并且真的必须在没有副本的情况下施放一个...
您始终可以编写一个函数来做两件事:首先,检查所有值是否不为空,如果是,则 return 转换为非空的数组。这有点 hacky,但之所以安全,只是因为区别在于可空性。
首先,在 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>
}
然后使用这个函数 new 函数进行转换(元素检查为非空转换):
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.asNotNull() // magic!
val upperStrings = strings.map { it.toUpperCase() } // no error
但即使谈论最后一个选项我也觉得很脏。
如果我创建一个数组,然后填充它,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 数组?
无法将此告知编译器。变量的类型在声明时就确定了。在这种情况下,变量被声明为可以包含空值的数组。
fill()方法不声明新变量,只是修改已有变量的内容,所以不会引起变量类型的改变。
一条经验法则:如果有疑问,请明确指定类型(对此有一个特殊的重构):
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"}
为什么填充数组工作正常
填充数组在调用期间从用作第二个参数的 lambda 推断数组的类型:
val strings = Array(10000, {"string"})
产生Array<String>
val strings = Array(10000, { it -> if (it % 2 == 0) "string" else null })
产生Array<String?>
因此,更改 =
左侧与 lambda 不匹配的声明没有任何帮助。有冲突就报错
如何使 arrayOfNulls 工作
对于 arrayOfNulls
问题,他们将您指定给调用的类型 arrayOfNulls<String>
在函数签名中用作泛型类型 T
和函数 arrayOfNulls
returns 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()
但是,如果你不能停止使用数组的任务,并且真的必须在没有副本的情况下施放一个...
您始终可以编写一个函数来做两件事:首先,检查所有值是否不为空,如果是,则 return 转换为非空的数组。这有点 hacky,但之所以安全,只是因为区别在于可空性。
首先,在 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>
}
然后使用这个函数 new 函数进行转换(元素检查为非空转换):
val nullableStrings = arrayOfNulls<String>(10000).apply { fill("hello") }
val strings = nullableStrings.asNotNull() // magic!
val upperStrings = strings.map { it.toUpperCase() } // no error
但即使谈论最后一个选项我也觉得很脏。