# 类型检查与类型转换 在 Kotlin 中, 你可以在运行时对类型进行两种操作: 检查对象是否为特定类型, 或将对象转换为另一种类型. 类型 检查 帮助你确认正在处理的对象的种类, 而类型 转换* 则尝试将对象转换为另一种类型. > **Tip:** > 关于 泛型 的类型检查和转换, 例如 `List`, `Map`, 请参见 [泛型的类型检查和转换](generics.html#generics-type-checks-and-casts). ## 使用 `is` 与 `!is` 操作符进行类型检查 使用 `is` 操作符(或相反的 `!is` 操作符), 在运行时检查对象是否匹配某个类型: ```KOTLIN fun main() { val input: Any = "Hello, Kotlin" if (input is String) { println("Message length: ${input.length}") // 输出结果为: Message length: 13 } if (input !is String) { // 等价于 !(input is String) println("Input is not a valid message") } else { println("Processing message: ${input.length} characters") // 输出结果为: Processing message: 13 characters } } ``` 也可以使用 `is` 和 `!is` 操作符, 检查对象是否匹配某个子类型: ```KOTLIN interface Animal { val name: String fun speak() } class Dog(override val name: String) : Animal { override fun speak() = println("$name says: Woof!") } class Cat(override val name: String) : Animal { override fun speak() = println("$name says: Meow!") } //sampleStart fun handleAnimal(animal: Animal) { println("Handling animal: ${animal.name}") animal.speak() // 使用 is 操作符检查子类型 if (animal is Dog) { println("Special care instructions: This is a dog.") } else if (animal is Cat) { println("Special care instructions: This is a cat.") } } //sampleEnd fun main() { val pets: List = listOf( Dog("Buddy"), Cat("Whiskers"), Dog("Rex") ) for (pet in pets) { handleAnimal(pet) println("---") } // 输出结果为: // Handling animal: Buddy // Buddy says: Woof! // Special care instructions: This is a dog. // --- // Handling animal: Whiskers // Whiskers says: Meow! // Special care instructions: This is a cat. // --- // Handling animal: Rex // Rex says: Woof! // Special care instructions: This is a dog. // --- } ``` 这个示例使用 `is` 操作符检查, `Animal` 类实例是否为子类型 `Dog` 或 `Cat`, 来打印相关的护理说明. 你可以检查一个对象是否是其声明类型的超类型, 但这其实没有意义, 因为答案永远为 true. 每个类实例本来就已经是其超类型的实例. > **Tip:** > 要在运行时识别对象的类型, 请参见 [反射(Reflection)](reflection.html). ## 类型转换 在 Kotlin 中, 将对象的类型转换为另一种类型, 这称为 类型转换. 在某些情况下, 编译器会自动为你进行类型转换. 这称为智能类型转换. 如果需要显式的转换类型, 请使用 `as?` 或 `as` [类型转换操作符](#unsafe-cast-operator). ## 智能类型转换 对不可变值, 编译器会追踪它的类型检查和 [显式的类型转换](#unsafe-cast-operator), 然后自动插入隐式的(安全的)类型转换: ```KOTLIN fun logMessage(data: Any) { // data 被自动转换为 String 类型 if (data is String) { println("Received text: ${data.length} characters") } } fun main() { logMessage("Server started") // 输出结果为: Received text: 14 characters logMessage(404) } ``` 如果一个相反的类型检查导致了 return, 此时编译器足够智能, 能够判断出转换处理是安全的: ```KOTLIN fun logMessage(data: Any) { // data 被自动转换为 String 类型 if (data !is String) return println("Received text: ${data.length} characters") } fun main() { logMessage("User signed in") // 输出结果为: Received text: 14 characters logMessage(true) } ``` ### 控制流 智能类型转换不仅能够用于 `if` 条件表达式, 还能用于 [when 表达式](control-flow.html#when-expressions-and-statements): ```KOTLIN fun processInput(data: Any) { when (data) { // data 被自动转换为 Int 类型 is Int -> println("Log: Assigned new ID ${data + 1}") // data 被自动转换为 String 类型 is String -> println("Log: Received message \"$data\"") // data 被自动转换为 IntArray 类型 is IntArray -> println("Log: Processed scores, total = ${data.sum()}") } } fun main() { processInput(1001) // 输出结果为: Log: Assigned new ID 1002 processInput("System rebooted") // 输出结果为: Log: Received message "System rebooted" processInput(intArrayOf(10, 20, 30)) // 输出结果为: Log: Processed scores, total = 60 } ``` 以及 [while 循环](control-flow.html#while-loops): ```KOTLIN sealed interface Status data class Ok(val currentRoom: String) : Status data object Error : Status class RobotVacuum(val rooms: List) { var index = 0 fun status(): Status = if (index < rooms.size) Ok(rooms[index]) else Error fun clean(): Status { println("Finished cleaning ${rooms[index]}") index++ return status() } } fun main() { //sampleStart val robo = RobotVacuum(listOf("Living Room", "Kitchen", "Hallway")) var status: Status = robo.status() while (status is Ok) { // 编译器将 status 智能类型转换为 OK 类型, // 因此可以访问 currentRoom 属性. println("Cleaning ${status.currentRoom}...") status = robo.clean() } // 输出结果为: // Cleaning Living Room... // Finished cleaning Living Room // Cleaning Kitchen... // Finished cleaning Kitchen // Cleaning Hallway... // Finished cleaning Hallway //sampleEnd } ``` 在这个示例中, 封闭接口 `Status` 有两个实现: 数据类 `Ok` 和数据对象 `Error`. 只有数据类 `Ok` 才有 `currentRoom` 属性. 当 `while` 循环条件计算结果为 true 时, 编译器将 `status` 变量智能类型转换为 `Ok` 类型, 使得循环体内可以访问 `currentRoom` 属性. 如果你声明一个 `Boolean` 类型的变量, 然后在你的 `if`, `when`, 或 `while` 条件中使用它, 那么编译器收集的关于这个变量的所有信息, 在对应的代码块中都可以用于智能类型转换. 当你想要将布尔条件抽取到变量中时, 这个功能会很有用. 之后, 你可以给变量一个有意义的名字, 这样可以提高你的代码的可读性, 并可以在之后的代码中重用这个变量. 例如: ```KOTLIN class Cat { fun purr() { println("Purr purr") } } //sampleStart fun petAnimal(animal: Any) { val isCat = animal is Cat if (isCat) { // 编译器能够得到关于 isCat 的信息, // 因此它知道 animal 已经被智能转换为 Cat 类型. // 所以, 可以调用 purr() 函数. animal.purr() } } fun main(){ val kitty = Cat() petAnimal(kitty) // 输出结果为: Purr purr } //sampleEnd ``` ### 逻辑操作符 对于 `&&` 和 `||` 操作符, 如果在操作符左侧进行了(通常的或相反的)类型检查, 那么编译器能够在右侧进行智能类型转换: ```KOTLIN // 在 `||` 的右侧, x 被自动转换为 String 类型 if (x !is String || x.length == 0) return // 在 `&&` 的右侧, x 被自动转换为 String 类型 if (x is String && x.length > 0) { print(x.length) // x 被自动转换为 String 类型 } ``` 如果你将对象的多个类型检查用 `and` 操作符 (`&&`) 组合起来, 编译器会将对象同时智能类型转换为所有被检查的类型. 详情请参见 [交集类型](#intersection-types). 如果你将对象的多个类型检查用 `or` 操作符 (`||`) 组合起来, 智能类型转换的结果会是这些类型最接近的共通超类型: ```KOTLIN interface Status { fun signal() {} } interface Ok : Status interface Postponed : Status interface Declined : Status fun signalCheck(signalStatus: Any) { if (signalStatus is Postponed || signalStatus is Declined) { // signalStatus 被智能类型转换为共通超类型 Status signalStatus.signal() } } ``` > **Note:** > 共通超类型是 [联合类型(Union Type)](https://en.wikipedia.org/wiki/Union_type) 的一种 近似. 联合类型 [在 Kotlin 中目前不支持](https://youtrack.jetbrains.com/issue/KT-13108/Denotable-union-and-intersection-types). ### 交集类型(Intersection Type) 当编译器通过多个 `&&` 检查对一个对象进行智能类型转换时, 它会推断出一个 [交集类型(Intersection Type)](https://kotlinlang.org/spec/type-system.html#intersection-types). 这是一个内部的类型, 同时满足所有被检查的约束: ```KOTLIN interface Bird { fun fly() } interface Fish { fun swim() } fun describe(animal: Any) { // 推断为 Bird 和 Fish 类型 if (animal is Bird && animal is Fish) { // 访问 fly() 和 swim(), 不需要进一步的检查和智能类型转换 animal.fly() animal.swim() } } ``` 交集类型是 "不可明确表示的类型"(Non-denotable). 它们只存在于编译器的内部类型系统中, 用于在类型检查期间保存类型信息. 你不能在 Kotlin 代码中直接书写它们. 你可能会在编译器错误信息和 IDE 提示信息中遇到交集类型, 通常显示为 `A & B`. 唯一的例外是 `T & Any`, 它声明一个 [确定不为 null 的类型](generics.html#definitely-non-nullable-types). 这是一个专门保留的语法, 用于将类型参数与 `Any` 结合. ```KOTLIN fun T.assertNotNull(): T & Any = this ?: throw IllegalStateException("null value") ``` ### 内联函数 对传递给 [内联函数](inline-functions.html) 的 Lambda 函数中捕获的变量, 编译器能够进行智能类型转换. 内联函数会被当作具有隐含的 [callsInPlace](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.contracts/-contract-builder/calls-in-place.html) 契约(Contract). 这就意味着, 传递给内联函数的任何 Lambda 函数都会被原地调用(call in place). 由于 Lambda 函数被原地调用, 因此编译器知道 Lambda 函数不会泄露它的函数体中所包含的任何变量的引用. 编译器使用这些信息, 以及其它分析, 决定对捕获的变量能否安全的进行智能类型转换. 例如: ```KOTLIN interface Processor { fun process() } inline fun inlineAction(f: () -> Unit) = f() fun nextProcessor(): Processor? = null fun runProcessor(): Processor? { var processor: Processor? = null inlineAction { // 编译器知道 processor 是一个局部变量, inlineAction() 是一个内联函数, // 因此对 processor 的引用不会泄露. // 所以, 对 processor 可以安全的进行智能类型转换. // 如果 processor 不为 null, processor 会被智能类型转换 if (processor != null) { // 编译器知道 processor 不为 null, 因此不需要安全调用 processor.process() } processor = nextProcessor() } return processor } ``` ### 异常处理 智能类型转换信息会被传递给 `catch` 和 `finally` 代码块. 这能够让你的代码更加安全, 因为编译器会追踪你的对象是不是可为 null 的类型. 例如: ```KOTLIN //sampleStart fun testString() { var stringInput: String? = null // stringInput 被智能类型转换为 String 类型 stringInput = "" try { // 编译器知道 stringInput 不为 null println(stringInput.length) // 输出结果为: 0 // 编译器丢弃 stringInput 之前的智能类型转换信息. // 现在 stringInput 类型为 String?. stringInput = null // 触发异常 if (2 > 1) throw Exception() stringInput = "" } catch (exception: Exception) { // 编译器知道 stringInput 可以为 null // 因此 stringInput 继续保持可为 null 的类型. println(stringInput?.length) // 输出结果为: null } } //sampleEnd fun main() { testString() } ``` ### 智能类型转换的前提条件 只在编译器能够确保变量在检查和使用之间不会改变的情况下, 智能类型转换才有效. 在以下条件下可以使用智能类型转换: | `val` 局部变量 | 永远有效, 但 [局部的委托属性](delegated-properties.html) 例外. | | `val` 属性 | 如果属性是 `private` 的, 或 `internal` 的, 或者类型检查处理与属性定义出现在同一个 [模块(module)](visibility-modifiers.html#modules) 内, 那么智能类型转换是有效的. 对于 `open` 属性, 或存在自定义 get 方法的属性, 智能类型转换是无效的. | | `var` 局部变量 | 如果在类型检查语句与变量使用语句之间, 变量没有被改变, 而且它没有被 Lambda 表达式捕获并在 Lambda 表达式内修改它, 并且它不是一个局部的委托属性, 那么智能类型转换是有效的. | | `var` 属性 | 永远无效, 因为其他代码随时可能改变变量值. | ## `as` 与 `as?` 类型转换操作符 Kotlin 有两个类型转换操作符: `as` 和 `as?`. 两者都可以用于类型转换, 但行为不同. 如果使用 `as` 操作符进行转换, 失败时会在运行期抛出 `ClassCastException`. 因此它也被称为 不安全 操作符. 你可以在转换为非 null 类型时使用 `as`: ```KOTLIN fun main() { val rawInput: Any = "user-1234" // 成功转换为 String 类型 val userId = rawInput as String println("Logging in user with ID: $userId") // 输出结果为: Logging in user with ID: user-1234 // 触发 ClassCastException val wrongCast = rawInput as Int println("wrongCast contains: $wrongCast") // Exception in thread "main" java.lang.ClassCastException } ``` 如果改用 `as?` 操作符, 当转换失败时, 操作符会返回 `null`. 因此它也被称为 安全 操作符: ```KOTLIN fun main() { val rawInput: Any = "user-1234" // 成功转换为 String 类型 val userId = rawInput as? String println("Logging in user with ID: $userId") // 输出结果为: Logging in user with ID: user-1234 // 将 null 值赋给 wrongCast val wrongCast = rawInput as? Int println("wrongCast contains: $wrongCast") // 输出结果为: wrongCast contains: null } ``` 要安全地转换可为 null 的类型, 请使用 `as?` 操作符, 以防止转换失败时触发 `ClassCastException`. 你 可以 将 `as` 与可为 null 的类型一起使用. 这样的操作允许结果为 `null`, 但如果转换不成功, 仍然会抛出 `ClassCastException`. 因此, `as?` 是更安全的选择: ```KOTLIN fun main() { val config: Map = mapOf( "username" to "kodee", "alias" to null, "loginAttempts" to 3 ) // 不安全地转换为可为 null 的 String val username: String? = config["username"] as String? println("Username: $username") // 输出结果为: Username: kodee // 不安全地将 null 值转换为可为 null 的 String val alias: String? = config["alias"] as String? println("Alias: $alias") // 输出结果为: Alias: null // 转换为可为 null 的 String 失败, 抛出 ClassCastException // val unsafeAttempts: String? = config["loginAttempts"] as String? // println("Login attempts (unsafe): $unsafeAttempts") // Exception in thread "main" java.lang.ClassCastException // 转换为可为 null 的 String 失败, 返回 null val safeAttempts: String? = config["loginAttempts"] as? String println("Login attempts (safe): $safeAttempts") // 输出结果为: Login attempts (safe): null } ``` ### 向上转换和向下转换 在 Kotlin 中, 你可以将对象转换为超类型或子类型. 将对象转换为其超类实例的操作称为 向上转换(upcasting). 向上转换不需要任何特殊的语法或转换操作符. 例如: ```KOTLIN interface Animal { fun makeSound() } class Dog : Animal { // 实现 makeSound() 的行为 override fun makeSound() { println("Dog says woof!") } } fun printAnimalInfo(animal: Animal) { animal.makeSound() } fun main() { val dog = Dog() // 将 Dog 实例向上转换为 Animal printAnimalInfo(dog) // 输出结果为: Dog says woof! } ``` 在这个示例中, 当对 `Dog` 实例调用 `printAnimalInfo()` 函数时, 编译器将其向上转换为 `Animal`, 因为这是预期的参数类型. 由于实际对象仍然是 `Dog` 实例, 编译器动态地从 `Dog` 类中解析 `makeSound()` 函数, 打印 `"Dog says woof!"`. 在依赖抽象类型行为的 Kotlin API 中, 你经常会看到显式的向上转换. 在 Jetpack Compose 和 UI 工具包中也很常见, 这些工具包通常将所有 UI 元素视为超类型, 然后对特定子类进行操作: ```KOTLIN val textView = TextView(this) textView.text = "Hello, View!" // 从 TextView 向上转换为 View val view: View = textView // 使用 View 的函数 view.setPadding(20, 20, 20, 20) // Activity 期望 View 类型 setContentView(view) ``` 将对象转换为其子类实例的操作称为 向下转换(downcasting). 由于向下转换可能不安全, 你需要使用显式的转换操作符. 为了避免转换失败时抛出异常, 我们推荐使用安全转换操作符 `as?`, 当转换失败时返回 `null`: ```KOTLIN interface Animal { fun makeSound() } class Dog : Animal { override fun makeSound() { println("Dog says woof!") } fun bark() { println("BARK!") } } fun main() { // 使用 Dog 实例创建 animal 变量, 类型为 Animal val animal: Animal = Dog() // 将 animal 安全的向下转换为 Dog 类型 val dog: Dog? = animal as? Dog // 使用安全调用, 当 dog 不为 null 时调用 bark() dog?.bark() // 输出结果为: "BARK!" } ``` 在这个示例中, `animal` 被声明为 `Animal` 类型, 但它保存一个 `Dog` 实例. 代码将 `animal` 安全的转换为 `Dog` 类型, 并使用 [安全调用](null-safety.html#safe-call-operator) (`?.`) 来访问 `bark()` 函数. 在序列化处理中, 如果要将基类反序列化为特定的子类型, 会用到向下转换. 在使用 Java 库时, 如果返回类型为超类型的对象, 而你需要在 Kotlin 中转换为子类型, 那么也经常用到向下转换.