kotlin更多語(yǔ)言結(jié)構(gòu)——>類型檢測(cè)與類型轉(zhuǎn)換 is 與 as
is 與 !is 操作符
我們可以在運(yùn)行時(shí)通過(guò)使用 is 操作符或其否定形式 !is 來(lái)檢測(cè)對(duì)象是否符合給定類型:
if (obj is String) { print(obj.length) } if (obj !is String) { // 與 !(obj is String) 相同 print("Not a String") } else { print(obj.length) }
智能轉(zhuǎn)換
在許多情況下,不需要在 Kotlin 中使用顯式轉(zhuǎn)換操作符,因?yàn)榫幾g器跟蹤不可變值的 is -檢測(cè)以及顯式轉(zhuǎn)換, 并在需要時(shí)自動(dòng)插入(安全的)轉(zhuǎn)換:
fun demo(x: Any) { if (x is String) { print(x.length) // x 自動(dòng)轉(zhuǎn)換為字符串 } }
編譯器足夠聰明,能夠知道如果反向檢測(cè)導(dǎo)致返回那么該轉(zhuǎn)換是安全的:
if (x !is String) return print(x.length) // x 自動(dòng)轉(zhuǎn)換為字符串
或者在 && 和 || 的右側(cè)
// `||` 右側(cè)的 x 自動(dòng)轉(zhuǎn)換為字符串 if (x !is String || x.length == 0) return // `&&` 右側(cè)的 x 自動(dòng)轉(zhuǎn)換為字符串 if (x is String && x.length > 0) { print(x.length) // x 自動(dòng)轉(zhuǎn)換為字符串 }
這些 智能轉(zhuǎn)換 用于 when-表達(dá)式 和 while-循環(huán) 也一樣:
when (x) { is Int -> print(x + 1) is String -> print(x.length + 1) is IntArray -> print(x.sum()) }
請(qǐng)注意,當(dāng)編譯器不能保證變量在檢測(cè)和使用之間不可改變時(shí),智能轉(zhuǎn)換不能用。更具體地,智能轉(zhuǎn)換能否適用 根據(jù)以下規(guī)則:
— val 局部變量——總是可以,局部委托屬性除外;
— val 屬性——如果屬性是 private 或 internal,或者該檢測(cè)在聲明屬性的同一模塊中執(zhí)行。智能轉(zhuǎn)換不適用于 open 的屬性或者具有自定義 getter 的屬性
— var 局部變量——如果變量在檢測(cè)和使用之間沒(méi)有修改、沒(méi)有在會(huì)修改它的 lambda 中捕獲、并且不是局部委托屬性;
— var 屬性——決不可能(因?yàn)樵撟兞靠梢噪S時(shí)被其他代碼修改)
“不安全的”轉(zhuǎn)換操作符
通常,如果轉(zhuǎn)換是不可能的,轉(zhuǎn)換操作符會(huì)拋出一個(gè)異常。因此,我們稱之為不安全的。Kotlin 中的不安全轉(zhuǎn)換由中綴操作符 as(參?operator precedence)完成:
?
val x: String = y as String
請(qǐng)注意,null不能轉(zhuǎn)換為 String 因該類型不是可空的,即如果 y 為空,上面的代碼會(huì)拋出一個(gè)異常。為了 讓這樣的代碼用于可空值,請(qǐng)?jiān)陬愋娃D(zhuǎn)換的右側(cè)使用可空類型:
val x: String? = y as String?
“安全的(” 可空)轉(zhuǎn)換操作符
為了避免拋出異常,可以使用安全轉(zhuǎn)換操作符 as?,它可以在失敗時(shí)返回 null
val x: String? = y as? String
請(qǐng)注意,盡管事實(shí)上 as? 的右邊是一個(gè)非空類型的 String ,但是其轉(zhuǎn)換的結(jié)果是可空的。
類型擦除與泛型檢測(cè)
Kotlin 在編譯時(shí)確保涉及泛型操作的類型安全性,而在運(yùn)行時(shí),泛型類型的實(shí)例并未帶有關(guān)于它們實(shí)際類型參 數(shù)的信息。例如,List<Foo> 會(huì)被擦除為 List<*> 。通常,在運(yùn)行時(shí)無(wú)法檢測(cè)一個(gè)實(shí)例是否屬于帶有某個(gè)類 型參數(shù)的泛型類型。
為此,編譯器會(huì)禁止由于類型擦除而無(wú)法執(zhí)行的 is 檢測(cè),例如 ints is List<Int> 或者 list is T(類 型參數(shù))。當(dāng)然,你可以對(duì)一個(gè)實(shí)例檢測(cè)星投影的類型
if (something is List<*>) { something.forEach { println(it) } // 這些項(xiàng)的類型都是 `Any?` }
類似地,當(dāng)已經(jīng)讓一個(gè)實(shí)例的類型參數(shù)(在編譯期)靜態(tài)檢測(cè),就可以對(duì)涉及非泛型部分做 is 檢測(cè)或者類型轉(zhuǎn) 換。請(qǐng)注意,在這種情況下,會(huì)省略尖括號(hào)
fun handleStrings(list: List<String>) { if (list is ArrayList) { // `list` 會(huì)智能轉(zhuǎn)換為 `ArrayList<String>` } }
省略類型參數(shù)的這種語(yǔ)法可用于不考慮類型參數(shù)的類型轉(zhuǎn)換:list as ArrayList
帶有具體化的類型參數(shù)的內(nèi)聯(lián)函數(shù)使其類型實(shí)參在每個(gè)調(diào)用處內(nèi)聯(lián),這就能夠?qū)︻愋蛥?shù)進(jìn)行 arg is T 檢 測(cè),但是如果 arg 自身是一個(gè)泛型實(shí)例,其類型參數(shù)還是會(huì)被擦除。例如:
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 } 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>>() // 破壞類型安全
非受檢類型轉(zhuǎn)換
如上所述,類型擦除使運(yùn)行時(shí)不可能對(duì)泛型類型實(shí)例的類型實(shí)參進(jìn)行檢測(cè),并且代碼中的泛型可能相互連接不 夠緊密,以致于編譯器無(wú)法確保類型安全。
即便如此,有時(shí)候我們有高級(jí)的程序邏輯來(lái)暗示類型安全。例如
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>
編譯器會(huì)對(duì)最后一行的類型轉(zhuǎn)換產(chǎn)生一個(gè)警告。該類型轉(zhuǎn)換不能在運(yùn)行時(shí)完全檢測(cè),并且不能保證映射中的值 是“Int”。
為避免未受檢類型轉(zhuǎn)換,可以重新設(shè)計(jì)程序結(jié)構(gòu):在上例中,可以使用具有類型安全實(shí)現(xiàn)的不同接口 DictionaryReader<T> 與 DictionaryWriter<T> 。可以引入合理的抽象,將未受檢的類型轉(zhuǎn)換從調(diào)用
代碼移動(dòng)到實(shí)現(xiàn)細(xì)節(jié)中。正確使用泛型型變也有幫助。
對(duì)于泛型函數(shù),使用具體化的類型參數(shù)可以使諸如 arg as T 這樣的類型轉(zhuǎn)換受檢,除非 arg 對(duì)應(yīng)類型的自身類型參數(shù)已被擦除。
可以通過(guò)在產(chǎn)生警告的語(yǔ)句或聲明上用注解 @Suppress("UNCHECKED_CAST") 標(biāo)注來(lái)禁止未受檢類型轉(zhuǎn)換警告
inline fun <reified T> List<*>.asListOfType(): List<T>? =
if (all { it is T })
@Suppress("UNCHECKED_CAST")
this as List<T> else
null
在 JVM 平臺(tái)中,數(shù)組類型( Array<Foo> )會(huì)保留關(guān)于其元素被擦除類型的信息,并且類型轉(zhuǎn)換為一個(gè)數(shù)組類型 可以部分受檢:元素類型的可空性與類型實(shí)參仍然會(huì)被擦除。例如,如果 foo 是一個(gè)保存了任何
List<*>(無(wú)論可不可空)的數(shù)組的話,類型轉(zhuǎn)換 foo as Array<List<String>?> 都會(huì)成功。

浙公網(wǎng)安備 33010602011771號(hào)