本文需要找几个问题。
① 什么时候会产生类型安全问题?
② 智能转换在什么时候是支持的
③ 如何规避类型安全问题
经过类型擦除后,依旧可以通过检测,确保当前的变量类型是确定的某个类型
会用到两个操作符
val myType as Date
在许多情况下,不需要在 Kotlin 中使用显式转换操作符,因为编译器跟踪不可变值的 is-检测以及显式转换,并在需要时自动插入(安全的)转换
fun demo(x: Any) {
if (x is String) {
print(x.length) // x 自动转换为字符串
}
}
if (x !is String) return
print(x.length) // x 自动转换为字符串
// `||` 右侧的 x 自动转换为字符串
if (x !is String || x.length == 0) return
// `&&` 右侧的 x 自动转换为字符串
if (x is String && x.length > 0) {
print(x.length) // x 自动转换为字符串
}
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}
换言之,Kotlin只要能通过is判断之后,就可以正常推导出正确的类型,自动转换
智能转换需要 变量在检测和使用时,是不可改变的
用as进行,会抛出异常
范例
val x: String = y as String
null不能转为String,若y为null,就会抛异常.
使用as?来进行,失败返回null
编译器会禁止由于类型擦除而无法执行的 is 检测
fun handleStrings(list: List<*>) {
if (list is List) {
// `list` 会智能转换为 `ArrayList`
}
}
这个函数对list进行了类型检测
因为类型擦除,这里就无法编译通过
报错提示
can check for instance of erased type: List
在运行时,泛型类型的实例并未带有关于它们实际类型参数的信息
例如, List 会被擦除为 List<*>
因此你只能 考虑不带类型参数的类型转换
比如
list as List
文字总是拗口的
inline fun Pair<*, *>.asPairOf(): Pair? {
if (first !is A || second !is B) return null
return first as A to second as B
}
这里first 跟 A在函数创建时,并不知道具体的类型
但在调用时,却能进行类型检测。
这就是内联
val somePair: Pair = "items" to listOf(1, 2, 3)
val stringToSomething = somePair.asPairOf()
val stringToInt = somePair.asPairOf()
val stringToList = somePair.asPairOf>()
val stringToStringList = somePair.asPairOf>() // 破坏类型安全!
为什么stringToStringList破坏类型安全呢?
因为List的类型会被擦除成List<*>
所以,这里这么写就不合适了
fun main() {
println("stringToSomething = " + stringToSomething)
println("stringToInt = " + stringToInt)
println("stringToList = " + stringToList)
println("stringToStringList = " + stringToStringList)
}
即编译器无法确保类型安全
原因可能是 代码中的泛型可能相互连接不够紧密
fun readDictionary(file: File): Map = file.inputStream().use {
TODO("Read a mapping of strings to arbitrary elements.")
}
// 我们已将存有一些 `Int` 的映射保存到该文件
val intsFile = File("ints.dictionary")
// Warning: Unchecked cast: `Map` to `Map`
val intsDictionary: Map = readDictionary(intsFile) as Map
最后一行会报个warning
Unchecked cast: `Map` to `Map`
类型转换不能在运行是完全检测
也不能保证映射中的值的Int
所以就报了warning
可以考虑重新设计代码结构
比如,将 未受检的类型转换转移到实现细节中
本范例,就将readDictionary的返回值类型改一下
fun readDictionary(file: File): Map = file.inputStream().use {
TODO("Read a mapping of strings to arbitrary elements.")
}
// 我们已将存有一些 `Int` 的映射保存到该文件
val intsFile = File("ints.dictionary")
// Warning: No cast needed
val intsDictionary: Map = readDictionary(intsFile) as Map
这里就是报No cast needed
将最后一行改成这样
val intsDictionary: Map = readDictionary(intsFile)
好了 No cast needed也不报了
这个范例,就直接讲readDIctionary的输出修改了。修改的是实现细节规避了类型检测不完全的问题。
回到前言的几个问题。
或因类型擦除,或因泛型,只要不能在检测、运行时,确定类型的都可能产生类型安全问题。
智能转换也是有条件的。
在《智能转换 - 注意》这一节中有提及。
除了,编码逻辑更加紧凑,还应该注意编译的提示,可以采用将类型转换转移到代码实现中去。用确定的结果来规避不确定的变化。