小言_互联网的博客

Kotlin-多变的类型_检测与转换

248人阅读  评论(0)

前言

本文需要找几个问题。
① 什么时候会产生类型安全问题?
② 智能转换在什么时候是支持的
③ 如何规避类型安全问题


什么是类型安全

经过类型擦除后,依旧可以通过检测,确保当前的变量类型是确定的某个类型


类型检测: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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场