类型检查和转换
在 Kotlin 中,可以执行类型检查以在运行时检查对象的类型。类型转换能够将对象转换为不同的类型。
is
和 !is
操作符
要执行运行时检查以确定对象是否符合给定类型,请使用 is
操作符或其否定形式 !is
。
kotlin
if (obj is String) {
print(obj.length)
}
// 等同于 !(obj is String)。
if (obj !is String) {
print("Not a String")
} else {
print(obj.length)
}
智能转换
在大多数情况下,不需要使用显式转换操作符,因为编译器会自动转换对象。这称为智能转换。
编译器会跟踪不可变值的类型检查和显式转换,并在必要时自动插入隐式(安全)转换。
kotlin
fun demo(x: Any) {
if (x is String) {
print(x.length) // x 自动转换为 String。
}
}
编译器甚至足够聪明,知道如果否定检查导致返回,则转换是安全的。
kotlin
if (x !is String) return
print(x.length) // x 自动转换为 String。
智能转换不仅适用于 if
条件表达式,还适用于 when
表达式和 while
循环。
kotlin
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}
如果在 if
、when
或 while
条件中使用布尔类型的变量之前声明它,那么编译器收集的有关该变量的任何信息都可以在相应的块中用于智能转换。
当您希望将布尔条件提取到变量中时,这非常有用。然后,您可以为变量赋予一个有意义的名称,这将提高代码的可读性,并使得稍后在代码中重用该变量成为可能。例如:
kotlin
class Cat {
fun purr() {
println("Purr purr")
}
}
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
}
如果在逻辑与 &&
或逻辑或 ||
运算符的左侧有一个类型检查(正向或负向),编译器可以在右侧执行智能类型转换。
kotlin
// 在 || 的右侧,x 自动被转换为 String 类型。
if (x !is String || x.length == 0) return
// 在 && 的右侧,x 自动被转换为 String 类型。
if (x is String && x.length > 0) {
print(x.length)
}
如果你将对象的类型检查与逻辑或 ||
运算符结合使用,智能类型转换会将它们转换为它们的最近公共超类型。
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()
}
}
编译器可以对传递给内联函数的 Lambda 函数中捕获的变量进行智能转换。
内联函数被视为具有隐式的 callsInPlace
契约。这意味着传递给内联函数的任何 Lambda 函数都是在原地调用的。由于 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
块。这使得您的代码更安全,因为编译器会跟踪您的对象是否具有可空类型。
kotlin
fun testString() {
var stringInput: String? = null
// stringInput 被智能转换为 String 类型。
stringInput = ""
try {
// 编译器知道 stringInput 不为 null。
println(stringInput.length) // 0
// 编译器拒绝之前的智能转换信息,
// 现在 stringInput 恢复为 String? 类型。
stringInput = null
// 触发异常。
if (2 > 1) throw Exception()
stringInput = ""
}
catch (exception: Exception) {
// 编译器知道 stringInput 可能为 null,因此 stringInput 保持可空类型。
println(stringInput?.length) // null
}
}
智能转换的前提条件
请注意,智能类型转换仅在编译器能够保证变量在检查和使用之间不会发生变化时才有效。
智能类型转换可以在以下条件下使用:
-
val
局部变量:- 始终有效,除了局部委托属性。
-
val
属性:-
如果属性是
private
、internal
,或者检查是在声明属性的同一模块中执行的。 -
智能转换不能用于
open
属性或具有自定义getter
的属性。
-
-
var
局部变量:- 如果变量在检查和使用之间未被修改,未被捕获在修改它的 Lambda 中,并且不是局部委托属性。
-
var
属性:- 永远不能使用,因为变量可能随时被其他代码修改。
不安全的转换操作符
要将对象显式转换为非空类型,请使用不安全的转换操作符 as
:
kotlin
val x: String = y as String
如果转换不可能,编译器会抛出异常。这就是为什么它被称为不安全的原因。
在前面的示例中,如果 y
为 null
,上面的代码会抛出异常。这是因为 null
不能转换为 String
,因为 String
不是可空类型。为了使示例适用于可能的空值,请在转换的右侧使用可空类型:
kotlin
val x: String? = y as String?
安全(可空)转换操作符
为了避免异常,请使用安全转换操作符 as?
,它在失败时返回 null
。
kotlin
val x: String? = y as? String
请注意,尽管 as?
的右侧是非空类型 String
,但转换的结果是可空的。
as?
是一种安全的类型转换方式,即使转换失败也不会抛出异常,而是返回 null
。