前言
本文需要找幾個問題。
① 什麼時候會產生類型安全問題?
② 智能轉換在什麼時候是支持的
③ 如何規避類型安全問題
什麼是類型安全
經過類型擦除後,依舊可以通過檢測,確保當前的變量類型是確定的某個類型
類型檢測: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的輸出修改了。修改的是實現細節規避了類型檢測不完全的問題。
小結
回到前言的幾個問題。
① 什麼時候會產生類型安全問題?
或因類型擦除,或因泛型,只要不能在檢測、運行時,確定類型的都可能產生類型安全問題。
② 智能轉換在什麼時候是支持的
智能轉換也是有條件的。
在《智能轉換 - 注意》這一節中有提及。
③ 如何規避類型安全問題
除了,編碼邏輯更加緊湊,還應該注意編譯的提示,可以採用將類型轉換轉移到代碼實現中去。用確定的結果來規避不確定的變化。