Kotlin-多變的類型_檢測與轉換

在這裏插入圖片描述

前言

本文需要找幾個問題。
① 什麼時候會產生類型安全問題?
② 智能轉換在什麼時候是支持的
③ 如何規避類型安全問題


什麼是類型安全

經過類型擦除後,依舊可以通過檢測,確保當前的變量類型是確定的某個類型


類型檢測:is

會用到兩個操作符

  • is
  • !is

類型轉換:as

val myType as Date


智能轉換

在許多情況下,不需要在 Kotlin 中使用顯式轉換操作符,因爲編譯器跟蹤不可變值的 is-檢測以及顯式轉換,並在需要時自動插入(安全的)轉換

來看個範例一
fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x 自動轉換爲字符串
    }
}
再看個範例二
if (x !is String) return

print(x.length) // x 自動轉換爲字符串
看個複雜點的
// `||` 右側的 x 自動轉換爲字符串
if (x !is String || x.length == 0) return

// `&&` 右側的 x 自動轉換爲字符串
if (x is String && x.length > 0) {
    print(x.length) // x 自動轉換爲字符串
}
再看一個
when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

換言之,Kotlin只要能通過is判斷之後,就可以正常推導出正確的類型,自動轉換

注意

智能轉換需要 變量在檢測和使用時,是不可改變的

  • val局部變量
    除了委託屬性,都可以
  • val屬性
    open或自定義getter的不行
    private、internal,檢測跟聲明屬性在同一模塊 可以
  • var 局部變量
    沒有委託
    檢測和使用之間沒有修改,沒有可能被修改(傳入後被修改也不行)
  • var 屬性
    不支持智能轉變

“不安全的”轉換操作符

用as進行,會拋出異常
範例

val x: String = y as String

null不能轉爲String,若y爲null,就會拋異常.


“安全的”(可空)轉換操作符

使用as?來進行,失敗返回null


類型擦除與泛型檢測

編譯器會禁止由於類型擦除而無法執行的 is 檢測

看個範例
fun handleStrings(list: List<*>) {
    if (list is List<String>) {
        // `list` 會智能轉換爲 `ArrayList<String>`
    }
}

這個函數對list進行了類型檢測
因爲類型擦除,這裏就無法編譯通過
報錯提示

can check for instance of erased type: List<String>

在運行時,泛型類型的實例並未帶有關於它們實際類型參數的信息
例如, List 會被擦除爲 List<*>
因此你只能 考慮不帶類型參數的類型轉換

比如

list as List

有具體類型參數的函數,在調用處會內聯

文字總是拗口的

先來看範例
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
    if (first !is A || second !is B) return null
    return first as A to second as B
}

這裏first 跟 A在函數創建時,並不知道具體的類型
但在調用時,卻能進行類型檢測。
這就是內聯

val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)

val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // 破壞類型安全!

爲什麼stringToStringList破壞類型安全呢?
因爲List的類型會被擦除成List<*>
所以,這裏這麼寫就不合適了

fun main() {
    println("stringToSomething = " + stringToSomething)
    println("stringToInt = " + stringToInt)
    println("stringToList = " + stringToList)
    println("stringToStringList = " + stringToStringList)
}

非受檢類型轉換

即編譯器無法確保類型安全
原因可能是 代碼中的泛型可能相互連接不夠緊密

還是看範例比較直白
fun readDictionary(file: File): Map<String, *> = file.inputStream().use { 
    TODO("Read a mapping of strings to arbitrary elements.")
}

// 我們已將存有一些 `Int` 的映射保存到該文件
val intsFile = File("ints.dictionary")

// Warning: Unchecked cast: `Map<String, *>` to `Map<String, Int>`
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>

最後一行會報個warning

Unchecked cast: `Map<String, *>` to `Map<String, Int>`

類型轉換不能在運行是完全檢測
也不能保證映射中的值的Int
所以就報了warning

怎麼規避呢?

可以考慮重新設計代碼結構
比如,將 未受檢的類型轉換轉移到實現細節中

本範例,就將readDictionary的返回值類型改一下

fun readDictionary(file: File): Map<String, Int> = file.inputStream().use { 
    TODO("Read a mapping of strings to arbitrary elements.")
}

// 我們已將存有一些 `Int` 的映射保存到該文件
val intsFile = File("ints.dictionary")

// Warning: No cast needed
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>

這裏就是報No cast needed

將最後一行改成這樣

val intsDictionary: Map<String, Int> = readDictionary(intsFile)

好了 No cast needed也不報了

這個範例,就直接講readDIctionary的輸出修改了。修改的是實現細節規避了類型檢測不完全的問題。


小結

回到前言的幾個問題。

① 什麼時候會產生類型安全問題?

或因類型擦除,或因泛型,只要不能在檢測、運行時,確定類型的都可能產生類型安全問題。

② 智能轉換在什麼時候是支持的

智能轉換也是有條件的。
在《智能轉換 - 注意》這一節中有提及。

③ 如何規避類型安全問題

除了,編碼邏輯更加緊湊,還應該注意編譯的提示,可以採用將類型轉換轉移到代碼實現中去。用確定的結果來規避不確定的變化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章