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