在 Koltin 中,开发者可以在运行时检查一个对象的类型,并且 也可以将一个对象从一个类型转为另外一个类型。
is 和 !is 操作符
使用 is 和 !is 操作符可以检查一个对象是否是开发者给定的类型:
kotlin
if (obj is String) {
print(obj.length)
}
if (obj !is String) { // Same as !(obj is String)
print("Not a String")
} else {
print(obj.length)
}
智能转换
大多数情况下,由于编译器能够进行自动类型转换,开发者不需要使用强制类型转换。编译器会追踪不可变类型的类检查和类型转换,并且在必要的时候进行隐式的安全转换:
kotlin
fun demo(x: Any) {
if (x is String) {
print(x.length) // x is automatically cast to String
}
}
编译器甚至非常智能,如果一个否定检查导致了返回,那么进行安全的类型转换:
kotlin
if (x !is String) return
print(x.length) // x is automatically cast to 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) {
// The compiler can access information about
// isCat, so it knows that animal was smart-cast
// to the type Cat.
// Therefore, the purr() function can be called.
animal.purr()
}
}
fun main(){
val kitty = Cat()
petAnimal(kitty)
// Purr purr
}
逻辑运算
如果在 if 语句的左侧有类型检查,那么编译器在 && 或者 || 运算符右侧可以进行智能类型转换:
kotlin
// x is automatically cast to String on the right-hand side of `||`
if (x !is String || x.length == 0) return
// x is automatically cast to String on the right-hand side of `&&`
if (x is String && x.length > 0) {
print(x.length) // x is automatically cast to String
}
如果开发者将对象的类型检查和 || 运算符结合使用,那么会将它们智能转换为它们最接近的共同超类:
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 is smart-cast to a common supertype Status
signalStatus.signal()
}
}
内联方法
编译器可以对传递给内联函数的 lambda 函数中捕获的变量进行智能转换。
内联函数会被视为具有隐式的 callsInPlace 约定。也就是说,传递给内联函数的所有 lambda 函数都会就地调用(即 lambda 函数的执行代码会直接嵌入到内联函数被调用的位置,而非单独创建函数对象后再调用)。由于 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 {
// The compiler knows that processor is a local variable and inlineAction()
// is an inline function, so references to processor can't be leaked.
// Therefore, it's safe to smart-cast processor.
// If processor isn't null, processor is smart-cast
if (processor != null) {
// The compiler knows that processor isn't null, so no safe call
// is needed
processor.process()
}
processor = nextProcessor()
}
return processor
}
异常处理
智能转换信息会传递到 catch 、 finally 块中。由于编译器会跟踪对象是否为可空类型,这一特性能让你的代码更安全。例如:
koltin
fun testString() {
var stringInput: String? = null
// stringInput is smart-cast to String type
stringInput = ""
try {
// The compiler knows that stringInput isn't null
println(stringInput.length)
// 0
// The compiler rejects previous smart cast information for
// stringInput. Now stringInput has the String? type.
stringInput = null
// Trigger an exception
if (2 > 1) throw Exception()
stringInput = ""
} catch (exception: Exception) {
// The compiler knows stringInput can be null
// so stringInput stays nullable.
println(stringInput?.length)
// null
}
}
智能类型转换前提
val 局部变量 | 在所有情况下都适用,局部委托属性除外 |
---|---|
val 属性 | 如果属性是 private(私有)、internal(内部)的,或者类型检查是在声明该属性的同一个模块中执行的,那么智能转换是可以的。但是智能类型转换不能用于open属性和用于自定义getter的属性 |
var 局部变量 | 在类型检查(如 is 判断)与变量使用之间,未被修改;未被捕获到会修改该变量的 lambda 函数中;不是局部委托属性。 |
var 属性 | 不适用 |
不安全的转换操作符
使用不安全的转换操作符 as ,可以将一个对象转为一个非空的类型:
koltin
val x: String = y as String
该转换会爆出异常,所以是不安全的。
如果 y 是 null 的,那么上面的代码就会出异常。这是因为 null 不可以转为 String。如果想要转一个可能为 null 的值,可以使用可空的类型:
kotlin
val x: String? = y as String?
安全的转换操作符
如果不想要出异常,可以使用 as? 操作符,这样的话转换失败的时候,会返回 null:
kotlin
val x: String? = y as? String