ITmob-Ly
发布于 2024-03-13 / 109 阅读
0

Kotlin 中的类型检查和类型转换详解

Kotlin-Blog

在 Kotlin 中编程时,有时需要进行类型检查或类型转换。执行类型检查以在运行时检查对象的类型; 类型转换将对象转换为不同的类型。

类型检查

使用 is 运算符或其否定形式 !is 在运行时进行类型检查,以确定对象是否是给定的类型:

if (obj is String) {
    print(obj.length)
}

if (obj !is String) {    // !is 等同于 !(obj is String)
    print("Not a String")
} else {
    print(obj.length)
}

类型转换

在 Kotlin 中有 3 种类型的转换:

  • 智能转换:编译器可以自动执行安全转换。
  • 不安全转换:显示的强制转换,如果开发人员判断错误,则会在运行时抛出异常,因此称为不安全的转换。
  • 安全转换:使用安全强制转换运算符 as?,避免转换失败是抛出异常。

智能转换

多数情况下不需要在 Kotlin 中显式的强制转换,因为编译器会推断不可变值类型检查和显式强制转换,并在必要时自动插入安全的转换:

fun demo(x: Any) {
    // 因为检查了 x 的类型,所以在 if 语句中编译器可以确定它的类型是什么并安全地进行了转换
    if (x is String) {
        print(x.length) // x 自动转换为 String
    }
}
  1. 编译器足够聪明,它知道如果否定检查导致返回,则强制转换是安全的
if (x !is String) return

print(x.length) // x 不是 String 导致返回,则 !is 之后的代码自动转换为 String 是安全的
  1. 或者如果 && 或 || 的左侧进行了类型检查,右侧可以确定类型也会自动转换
// 左侧进行了类型检查,只有 x 是 String 类型时右侧才会执行,则 x 自动转换为 String 类型
if (x !is String || x.length == 0) return

// x 是 String 类型时右侧才会执行,执行右侧代码时 x 的类型是明确的,所以 x 自动转换为 String 类型
if (x is String && x.length > 0) {
    print(x.length) // x is automatically cast to String
}
  1. 智能转换也适用于 while 表达式和 while 循环中有类型判断的情况:
when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}
  1. 密封类加只能转换比枚举类更灵活

Kotlin 中的密封类(Sealed Class)是一种特殊的类,密封类可以让我们定义一组相关的类。

密封类通常用于代替枚举类,允许我们创建持有不同行为或数据的子类,通过密封类和智能转换的结合,密封类可以轻松实现相同父类的对象识别其具体类型并执行不同的子类操作:

package cn.itmob.sample

sealed class NetworkResult {
    class Success(private val response: String) : NetworkResult() {
        fun response() {
            println(response)
        }
    }

    data class Error(val message: String) : NetworkResult()
}

fun fetchNetworkData(result: NetworkResult) {
    when (result) {
        is NetworkResult.Success -> result.response()
        is NetworkResult.Error -> println(result.message)
    }
}

fun main() {
    fetchNetworkData(NetworkResult.Success("response string")) // response string
    fetchNetworkData(NetworkResult.Error("error message")) // error message
}

不安全转换

Kotlin 中的不安全强制转换由运算符 as 完成,请注意,只有当 100% 确定转换会成功时才应该使用它,而在其他情况下如果无法成功进行强制转换,则强制转换运算符会引发异常导致应用程序崩溃。

package cn.itmob.sample

fun main() {
    val y = "🙂"
    val z = Any()
    val x1: String = y as String // 正常转换
    val x2: String = z as String // 抛出异常:ClassCastException: class java.lang.Object cannot be cast to class java.lang.String
    println(x1)
    println(x2)    
}

使用 as 进行强制转换时还可以转换为可空的类型:

package cn.itmob.sample

import kotlin.Any

fun main() {
    val y: Any? = null
    val z: String? = null
    val x1: String = z as String // 抛出异常:NullPointerException: null cannot be cast to non-null type kotlin.String
    val x2: String? = y as String? // 正常转换
    println(x1)
    println(x2) // 输出:null
}

null 不可以转换为其他类型,会抛出异常。但是可空类型为 null 时可以转换为其他类型的可空类型,转换后仍为 null

  • 强制类型转换中的智能转换

上面的例子中通过 as 操作符进行类型转换,可以跳过通过 val 创建新值的过程。因为类型转换之后其类型是确定的了,也就符合了智能转换的要求,可以直接作为转换后的类型使用该值了:

package cn.itmob.sample

import kotlin.Any

fun main() {
    val x: Any = "https://itmob.cn"
    x as String
    println(x) // 输出:https://itmob.cn
}

安全转换

由于不安全转换可能抛出异常,应尽可能避免使用,为了避免异常,请使用安全强制转换运算符 as?,它在转换失败时会返回 null

package cn.itmob.sample

import kotlin.Any

fun main() {
    val y = Any()
    val x: String? = y as? String
    println(x) // 输出:null
}

as? 运算符右侧是非空的 String 类型,但是 as? 运算符的运算结果仍必须是可空的

如果 as? 运算符的返回值定义为非空的,则会抛出异常:

Type mismatch: inferred type is String? but String was expected

package cn.itmob.sample

import kotlin.Any

fun main() {
    val y = Any()
    val x: String = y as? String // Type mismatch: inferred type is String? but String was expected
    println(x)
}

更多相关内容参见:官方文档