1. 程式人生 > 程式設計 >關於Kotlin的自動型別轉換詳解

關於Kotlin的自動型別轉換詳解

前言

Kotlin 1.4 正式版在好早以前就已經發布了。關於那些“看得見”的新特性,比如SAM轉換、引數列表末尾的可選逗號什麼的,已經有無數文章介紹過了。所以本文打算介紹一些可能是鮮為人知的、Kotlin 官方團隊偷偷塞進 1.4 的新特性。

不過單獨講這些東西會顯得文章太過單薄,於是我打算把其他相似的東西拉一起湊湊字數。

本文使用的 Kotlin 版本為 Kotlin 1.4。

本文要講的東西

看題目就知道了,Kotlin 裡自動型別轉換(automatic type conversion)。這裡講的不是 「把一個 String 轉成 Any,再轉成 String」 這種和子型別有關的東西,當然也不是 Smart Cast,而是兩個不相容的型別之間的轉換,比如說 Int 轉成 Long,如下文所示。

數值轉換

一般地,在 Kotlin 裡我們不能像 Java 一樣直接把一個 Int 型別的東西賦值給 Long 型別的變數,因為它們之間並不具有子型別關係。像下面這樣會得到一個編譯錯誤:

val int: Int = 555
val long: Long = int // 編譯錯誤!
println(long)

你需要呼叫標準庫提供給你的那些 toXXX 函式把數值轉換成其他型別的數值。

val int: Int = 555
val long: Long = int.toLong() // OK
println(long)

Kotlin 官方團隊曾經表示過不喜歡隱式(implicit)的東西,關於數值的隱式型別轉換也包括在內。這就導致了使用 Kotlin 在進行一些關於數值方面的操作時,有時候會寫出一些看起來無比蛋疼的程式碼。

Bennyhuo:就是有時候寫點兒計算比較多的程式碼,滿篇的 toFloat toDouble。

不一般地,我們可以使用 @Suppress 來搞事:

val int: Int = 233
@Suppress("TYPE_MISMATCH")
val long: Long = int
println(long) // 233

這個程式碼是可以跑起來的,而且你真的可以從位元組碼裡看到那個把 Int 轉成 Long 的指令 I2L。

不過我不確定 Kotlin 的其他 target 是否能這樣用,我也不保證這樣寫完全不會出問題。(這裡是關於 @Suppress 的免責宣告,請讀者自行腦補)

SAM Conversion

SAM 轉換也是一種自動型別轉換。它把一個 lambda 表示式(具有函式型別)轉成某個具體的介面型別。

fun interface ISome {
 fun some()
}

fun useSome(some: ISome) {}

useSome { println("some") }

在我的另一篇文章裡有更詳細的介紹。

如果讀者不同意這個說法,可以選擇跳過本小節內容。

Coercion to Unit

我們都知道 Kotlin 的 lambda 表示式是使用裡面最後一個表示式的值來作為 lambda 的返回值的。比如這樣:

val block = { "yeah" }

block 的型別是 () -> String。

然後我們來看看這樣的情況:

fun test(block: () -> Unit) {
 println(block())
}

test { "yeah" } // 輸出 Unit

相信很多人都熟悉這樣的寫法。

在某些初學者的眼裡這看起來像是把一個 () -> String 型別的 lambda 傳給了需要 () -> Unit 型別的函式。

這就是 coercion to unit,一個很久以前就存在的特性,可以理解為編譯器自動幫你在 lambda 表示式的最後加了一行 Unit,把本來應該是() -> String 型別的 lambda 變成了 () -> Unit 型別。

在 Kotlin 1.4 版本,這個特性得到了進化,你甚至可以這樣寫:

fun test(block: () -> Unit) {
 println(block())
}

fun some(): String {
 return "str"
}

// 需要 Kotlin 1.4 版本
test(::some) // 輸出 Unit

編譯器幫你把 () -> String 型別的函式引用轉成了 () -> Unit。

Unit Conversion

警告:這是一項未完成的特性!

新增編譯器引數 -XXLanguage:+UnitConversion,你就開啟了一個 Kotlin 官方團隊偷偷塞進 1.4 版本的未完成的新特性。

這個特性允許你寫出這樣的程式碼:

fun test(block: () -> Unit) {
 println(block())
}

fun some(block: () -> String) {
 test(block) // 這裡是重點
 // 如果你不加那個編譯器引數,會報錯
}

fun main() {
 some { "str" }
 // 理論上會輸出 Unit
}

在函式 some 裡把一個 () -> String 傳給了 test 函式,可以看出來這個特性其實和 coercion to unit 是差不多的。

理論上這樣的程式碼執行時會輸出 Unit,但是目前由於該特性的程式碼生成沒寫好,得不到預期的結果。

另外,在開啟了這個特性後,() -> String 並不會成為 () -> Unit 的子型別,它們依然是兩個不相容的型別。

Suspend Conversion

警告:這是一項未完成的特性!

這是本文要介紹的第二個 Kt 官方團隊偷偷塞進 1.4 版本的未完成的新特性。

比如說我們有這樣的一個函式:

fun test(f: suspend () -> Unit) {
 // do something with f
}

我們可以這樣呼叫它:

test { println("hi") } 

但是這樣不行:

val f = { println("hi") }
test(f) // 編譯錯誤

編譯器會告訴你型別不匹配,f 是 () -> Unit 型別,test 函式需要 suspend () -> Unit 型別的引數。

當你添加了編譯器引數 -XXLanguage:+SuspendConversion,就可以讓上面的程式碼通過編譯。

也就是說這個特性可以幫你把普通函式型別的值轉成 suspend 函式型別。

當然由於這是未完成的功能,即使可以通過編譯,但是跑起來還是會炸。

這個特性或許會在 Kotlin 1.5 版本完工,但請不要抱有期待。

結尾

我並不想討論「為什麼要加這種奇怪的特性」之類的話題。

不可否認的是,在有限的程式設計師生涯中,這些新特性可能一次也用不上。上面提到的問題也都有相應的 workaround,不需要新特性也可以寫出等價的程式碼,就是沒有那麼優雅罷了(

到此這篇關於關於Kotlin自動型別轉換的文章就介紹到這了,更多相關Kotlin自動型別轉換內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!