Kotlin 1.6 正式釋出,帶來哪些新特性?
theme: smartblue highlight: github
11月16日,Kotlin 1.6 正式對外發布,這個版本中都有哪些新的語法特性?
- 更安全的when語句(exhaustive when statements)
- 掛起函式型別可作父類 (suspending functions as supertypes )
- 普通函式轉掛起函式(suspend conversion)
- Builder函式更加易用
- 遞迴泛型的型別推導
- 註解相關的一些優化
1. 更安全的 when 語句
Kotlin 的 when 關鍵字允許我們在 case 分支中寫表示式或者語句。1.6 之前在 case 分支寫語句時存在安全隱患:
```kotlin // 定義列舉 enum class Mode { ON, OFF } val x: Mode = Mode.ON
// when表示式 val result = when(x) { Mode.ON -> 1 // case 中是一個表示式 Mode.OFF -> 2 }
// when語句 when(x) { Mode.ON -> println("ON") // case 是一個語句 Mode.OFF -> println("OFF") } ``` 下表說明了編譯器針對 when 關鍵字的檢查內容
|x 的型別| 列舉、密封類/介面、Bool型等(可窮舉型別)|不可窮舉型別 | |--|--|--| |when表示式|case 必須窮舉所有分支,或者新增 else,否則編譯出錯|Case 分支必須包含 else,否則編譯出錯| |when語句|case 可以不窮舉所有分支,不會報錯|同上|
可見,當 x 是可窮舉型別時,編譯器對when表示式
的檢查比較嚴謹,如果 case
不能窮舉所有分支或者缺少 else
,編譯器會報錯如下:
ERROR: 'when' expression must be exhaustive, add necessary 'is TextMessage' branch or 'else' branch instead
但編譯器對於 when語句
的檢查卻不夠嚴謹,即使沒有窮舉所有分支也不會報錯,不利於開發者寫出安全的程式碼:
kotlin
// when語句
when(x) { // WARNING: [NON_EXHAUSTIVE_WHEN] 'when' expression on enum is recommended to be exhaustive, add 'OFF' branch or 'else' branch instead
Mode.ON -> println("ON") // case 是一個語句
}
Kotlin 1.6 起,當你在 When語句
中是可窮舉型別時必須處理所有分支,不能遺漏。考慮到歷史程式碼可能很多,為了更平穩的過渡,1.6 對 when語句
中沒有窮舉的 case
會首先給出 Warning
,從 1.7 開始 Warning 將變為 Error 要求開發者強制解決。
2. 掛起函式型別可作父類
Kotlin 中一個函式型別可以作為父類被繼承。
```kotlin
class MyFun
fun
Kotlin 程式碼中大量使用各種函式型別,許多方法都以函式型別作為引數。當你需要呼叫這些方法時,需要傳入一個函式型別的例項。而當你想在例項中封裝一些可複用的邏輯時,可以使用函式型別作為父類建立子類。
但是這種做法目前不適用於掛起函式,你無法繼承一個 suspend
函式型別的父類
```kotlin class C : suspend () -> Unit { // Error: Suspend function type is not allowed as supertypes }
C().startCoroutine(completion = object : Continuation
override fun resumeWith(result: Result<Unit>) {
TODO("Not yet implemented")
}
}) ```
但是以掛起函式作為引數或者 recevier 的方法還挺多的,所以 Kotlin 1.5.30 在 Preveiw 中引入了此 feature,這次 1.6 將其 Stable。 ```kotlin class MyClickAction : suspend () -> Unit { override suspend fun invoke() { TODO() } }
fun launchOnClick(action: suspend () -> Unit) {} ```
如上,你可以現在可以像這樣呼叫了 launchOnClick(MyClickAction())
。
需要注意普通函式型別作為父類是可以多繼承的 ```kotlin class MyClickAction : () -> Unit, (View) -> Unit { override fun invoke() { TODO("Not yet implemented") }
override fun invoke(p1: View) {
TODO("Not yet implemented")
}
} ``` 但是目前掛起函式作為父類不支援多繼承,父類列表中,既不能出現多個 suspend 函式型別,也不能有普通函式型別和suspend函式型別共存。
3. 普通函式轉掛起函式
這個 feature 也是與函式型別有關。
Kotlin 中為一個普通函式新增 suspend 是無害的,雖然編譯器會提示你沒必要這麼做。當一個函式簽名有一個 suspend 函式型別引數,但是也允許你傳入一個普通函式,在某些場景下是非常方便的。
```kotlin
//combine 的 transform 引數是一個 suspend 函式
public fun
suspend fun before4_1() { combine( flowA, flowB ) { a, b -> a to b }.collect { (a: Int, b: Int) -> println("$a and $b") } } ```
如上述程式碼所示,flow
的 combine
方法其引數 transform
型別是一個 suspend
函式,我們希望再次完成一個 Pair
的建立。這個簡單的邏輯本無需使用 suspend
,但在 1.4 之前只能像上面這樣寫。
Kotlin 1.4 開始,普通函式的引用可以作為 suspend
函式傳參,所以 1.4 之後可以改成下面的寫法,程式碼更簡潔:
kotlin
suspend fun from1_4() {
combine(
flowA, flowB, ::Pair
).collect { (a: Int, b: Int) ->
println("$a and $b")
}
}
1.4 之後仍然有一些場景中,普通函式不能直接轉換為 suspend 函式使用
```kotlin fun getSuspending(suspending: suspend () -> Unit) {}
fun suspending() {}
fun test(regular: () -> Unit) {
getSuspending { } // OK
getSuspending(::suspending) // OK from 1.4
getSuspending(regular) // NG before 1.6
}
比如上面 `getSuspending(regular)` 會報錯如下:
cmd
ERROR:The feature "suspend conversion" is disabled
```
Kotlin 1.6 起,所有場景的普通函式型別都可以自動轉換為 suspend 函式傳參使用,不會再看到上述錯誤。
4. Builder 函式更加易用
我們在構建集合時會使用一些 Builder函式
,比如 buildList
,buildMap
之類。
```kotlin
@ExperimentalStdlibApi
@kotlin.internal.InlineOnly
public inline fun
@kotlin.ExperimentalStdlibApi
val list = buildList
buildList
的實現中使用 @BuilderInterface
註解了 builderAction
這個 lambda 。這樣可以在呼叫時 buildList
通過 builderAction
內部的方法呼叫智慧推匯出泛型引數的型別,從而減少模板程式碼
```kotlin
//
//
但是 BuilderInterface
的型別推導限制比較多,比如 lambda 中呼叫的方法的簽名要求比較嚴格,必須引數是泛型且返回值沒有泛型,破壞了規則,型別推導失敗了。所以上面程式碼中 lambda 有 get()
呼叫時,就必須清楚的標記泛型型別。這使得集合類的 builder 函式使用起來不那麼靈活。
Kotlin 1.6 起 BuilderInterface 沒有了類似限制,對我們來說最直觀好處就是 Builder 函式內怎樣的呼叫都不會受限制,使用更加自由 ```kotlin val list = buildList { add("a") add("b") set(1, null) //OK val x = get(1) //OK if (x != null) { removeAt(1) //OK } }
val map = buildMap { put("a", 1) //OK put("b", 1.1) //OK put("c", 2f) //OK } ```
此 feature 在 1.5.30 也可以通過 新增 -Xunrestricted-builder-inference
編譯器選項生效,1.6 已經是預設生效了。
5. 遞迴泛型的型別推導
這個 feature 我們平常需求比較少。
Java 或者 Kotlin 中我們可以像下面這樣定義有遞迴關係的泛型,即泛型的上限是它本身
java
public class PostgreSQLContainer<SELF extends PostgreSQLContainer<SELF>> extends JdbcDatabaseContainer<SELF> {
//...
}
這種情況下的型別推導比較困難,Kotlin 1.5.30 開始可以只基於泛型的上線進行型別推導。
```kotlin
// Before 1.5.30
val containerA = PostgreSQLContainer
// With compiler option in 1.5.30 or by default starting with 1.6.0 val containerB = PostgreSQLContainer(DockerImageName.parse("postgres:13-alpine")) .withDatabaseName("db") .withUsername("user") .withPassword("password") .withInitScript("sql/schema.sql") ```
1.5.30 支援此 feature 需要新增 -Xself-upper-bound-inference
編譯選項, 1.6 開始預設支援。
6. 註解相關的一些優化
Kotlin 1.6 中對註解進行了諸多優化,在編譯器註解處理過程中將發揮作用
支援註解的例項化
kotlin
annotation class InfoMarker(val info: String)
fun processInfo(marker: InfoMarker) = ...
fun main(args: Array<String>) {
if (args.size != 0)
processInfo(getAnnotationReflective(args))
else
processInfo(InfoMarker("default"))
}
Java 的註解本質是實現了 Annotation
的介面,可以被繼承使用
```java @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface JavaClassAnno { String[] value(); }
public interface JavaClassAnno extends Annotation{ //... }
class MyAnnotation implements JavaClassAnno { // <--- works in Java
//...
}
但是在 Kotlin 中無法繼承使用,這導致有一些接受註解類的 API 在 Kotlin 側無法呼叫。
java
class MyAnnotationLiteral : JavaClassAnno { // <--- doesn't work in Kotlin (annotation can not be inherited)
//...
}
```
註解類可以例項化之後,可以呼叫接收註解類引數的 API,能夠與 Java 程式碼進行更好地相容
泛型引數可添加註解
```kotlin @Target(AnnotationTarget.TYPE_PARAMETER) annotation class BoxContent
class Box<@BoxContent T> {} ```
Kotlin 1.6 之後可以為泛型引數添加註解,這將為 KAPT / KSP 等註解處理器中提供方便。
可重複的執行時註解
Jdk 1.8 引入了 @java.lang.annotation.Repetable
元註解,允許同一個註解被新增多次。 Kotlin 也相應地引入了 @kotlin.annotation.Repeatable
,不過 1.6之前只能註解 @Retention(RetentionPolicy.SOURCE)
的註解,當非 SOURCE 的註解出現多次時,會報錯
ERROR: [NON_SOURCE_REPEATED_ANNOTATION] Repeatable annotations with non-SOURCE retention are not yet supported
此外,Kotlin 側程式碼也不能使用 Java 的 @Repeatable
註解來註解多次。
Kotlin1.6 開始,取消了只能用在 SOURCE 類註解的限制,任何型別的註解都可以出現多次,而且 Kotlin 側支援使用 Java 的 @Repeatable
註解
```kotlin @Repeatable(AttributeList.class) @Target({ElementType.TYPE}) @Retentioin(RetentionPolicy.RUNTIME) //雖然是 RUNTIME 註解 annotation class Attribute(val name: String)
@Attribute("attr1") //OK @Attribute("attr2") //OK class MyClass {} ```
最後
上述介紹的是 Kotlin1.6 在語法方面的一些新特性,大部分在 1.5.30 中作為 preview 功能已經出現過,這次在 1.6 中進行了轉正。除了新的語法特性,1.6 在各平臺 Compiler 上有諸多新內容,我們在平日開發中接觸不到本文就不介紹了。
更多內容參考:https://kotlinlang.org/docs/whatsnew16.html
- 告別KAPT!使用 KSP 為 Kotlin 編譯提速
- 探索 Jetpack Compose 核心:深入 SlotTable 系統
- 盤點 Material Design 3 帶來的新變化
- Compose 動畫邊學邊做 - 夏日彩虹
- Google I/O :Android Jetpack 最新變化(二) Performance
- Google I/O :Android Jetpack 最新變化(一) Architecture
- Google I/O :Android Jetpack 最新變化(四)Compose
- Google I/O :Android Jetpack 最新變化(三)UI
- 一文看懂 Jetpack Compose 快照系統
- 聊聊 Kotlin 代理的“缺陷”與應對
- AAB 扶正!APK 再見!
- 面試必備:Kotlin 執行緒同步的 N 種方法
- Jetpack MVVM 七宗罪之六:ViewModel 介面暴露不合理
- CreationExtras 來了,建立 ViewModel 的新方式
- Kotlin DSL 實戰:像 Compose 一樣寫程式碼
- 為什麼 RxJava 有 Single / Maybe 等單發資料型別,而 Flow 沒有?
- 使用整潔架構優化你的 Gradle Module
- 一道面試題:介紹一下 Fragment 間的通訊方式?
- 【程式碼吸貓】使用 Google MLKit 進行影象識別
- Kotlin 1.6 正式釋出,帶來哪些新特性?