Kotlin 类型检查与转换

在 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

原文链接

相关推荐
手机不死我是天子1 天前
《Android 核心组件深度系列 · 第 2 篇 Service》
android
前行的小黑炭1 天前
Compose页面切换的几种方式:Navigation、NavigationBar+HorizontalPager,会导致LaunchedEffect执行?
android·kotlin·app
前行的小黑炭1 天前
Android :Comnpose各种副作用的使用
android·kotlin·app
XeonYu1 天前
Kotlin 协程之 Flow 操作符大全
kotlin·flow·flow 操作符
BD_Marathon2 天前
【MySQL】函数
android·数据库·mysql
西西学代码2 天前
安卓开发---耳机的按键设置的UI实例
android·ui
maki0772 天前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架2 天前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid2 天前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl2 天前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea