大家好,我是青空,今天是kotlin入门系列的第3篇。今天给大家介绍的是kotlin的类型检测和转换。

android kotlin 对象null判断 kotlin类型推断_List

kotlin 中类型检测使用的是is关键字

“kotlin” is String // 是
“kotlin” !is String //不是

kotlin 中类型转换使用的是as关键字

123 as Long

什么是类型安全

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

智能转换

在许多情况下,不需要在 Kotlin 中使用显式转换操作符,因为编译器跟踪不可变值的 is-检测以及显式转换,并在需要时自动插入(安全的)转换

范例

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x 自动转换为字符串
    }
}
f (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) {
    // list 会智能转换为 ArrayList<String>
    }
}

这个函数对list进行了类型检测,因为类型擦除,这里就无法编译通过 。报错提示 “can check for instance of erased type: List”

在运行时,泛型类型的实例并未带有关于它们实际类型参数的信息。例如, 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>() // 破坏类型安全!

为什么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)

这个范例,就直接讲readDIctionary的输出修改了。修改的是实现细节,规避了类型检测不完全的问题。