🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

===================================================

也許你還在用 Kotlin,但是你是不是很久沒關注過 Kotlin 都有哪些更新了?實際上這兩年裡,自從 K2 開始,Kotlin 發布了不少版本,也增加了很多新的特性,今天我們主要就是彙總聊聊,有哪些是你錯過的新支援:

img

2.0 之後的 Kotlin 是全新的場景,因為 K2 不是一個普通的大版本更新,K2 屬於對編譯器的完全重寫,主要是統一中間表示(IR),也實作了更高效的語意分析,最重要的是提升了編譯速度,也強化了 KMP(Kotlin Multiplatform)場景,因此從 K2 時代開始,Kotlin 就進入了一個新的時代。

Kotlin 2.0.0

Kotlin 2.0.0 版本是 K2 編譯器的第一個穩定版本里程碑。前面已經提到,K2 大規模優化了 K1 在程式碼分析過程中過度依賴隱式延遲載入的行為,解決了在大規模複雜專案中經常導致記憶體溢位或編譯效率低下的問題。

K2

K2 主要是透過將程式碼分析拆解成一系列明確的階段(phases),從而提升處理的線性化與穩定性:

編譯相位 職責 目的
SUPER_TYPES 計算類的超型別階層 建立基礎型別拓撲,為後續推斷提速
TYPES 處理函式簽名、參數及顯式返回型別 確保介面契約在早期即完成驗證
STATUS 確定宣告的可見性與修飾符(如 open/final) 減少解析過程中的回溯需求
ARGUMENTS 解決函式呼叫時的參數綁定與重載決策 在複雜重載場景下提供一致行為

簡單來說,這種階段化設計可以讓 IntelliJ IDEA 與 Android Studio 的 K2 模式更穩定進行程式碼分析,減少 IDE 假死與高 CPU 使用情況。根據官方在 Anki-Android 等實際專案上的基準測試,K2 的效能提升如下:

測試指標 Kotlin 1.9.23 (K1) Kotlin 2.0.0 (K2) 性能提升幅度
全量構建耗時 (Clean Build) 57.7s 29.7s ~94%
增量編譯初始化耗時 0.126s 0.022s ~488%
增量編譯分析耗時 0.581s 0.122s ~376%

這些性能提升主要來自 K2 內部資料結構的精簡,以及對型別推斷演算法的優化,使編譯器能更智慧地跳過不必要的重複檢查。

如果你現在還覺得編譯慢、記憶體佔用高,是否該考慮還沒用 K2 的原因?

智慧型轉型(Smart Casts)

在 Kotlin 2.0 裡,智慧型轉型機制也有全面強化,主要解決了 K1 編譯器裡許多「邏輯缺陷」場景,例如:

局部變數與作用域擴展

在舊版本中,如果變數的型別檢查結果被儲存在一個臨時的 Boolean 變數,編譯器可能無法在後續的 if 區塊內識別出對應檢查;而在 K2 中可以跨越局部變數的賦值進行狀態追蹤:

fun petAnimal(animal: Any) {
    val isCat = animal is Cat
    if (isCat) {
        // Kotlin 2.0+:編譯器能溯源 isCat,將 animal 轉為 Cat
        animal.purr()
    }
}

內聯函式閉包中的智慧型轉型

K1 對於在 lambda 中修改的局部變數支援相當保守,即便 lambda 在內聯函式中執行也常失敗;但 K2 能夠辨識內聯函式的 lambda 不會發生逸出(no-escape),可以在內聯閉包中進行智慧型轉型:

fun indexOfMax(a: IntArray): Int? {
    var maxI: Int? = null
    a.forEachIndexed { i, value ->
        // K2:能識別 maxI 在內聯迴圈中的狀態
        if (maxI == null || value > a[maxI]) {
            maxI = i
        }
    }
    return maxI
}

例外情況的增強

K2 能把智慧型轉型資訊進一步傳遞到 catch 與 finally,並在邏輯或(||)運算後,嘗試將型別合併為共同超類,從而允許在更複雜的表達式後安全地存取成員:

interface Status { fun signal() {} }
interface Postponed : Status
interface Declined : Status

fun signalCheck(signalStatus: Any) {
    if (signalStatus is Postponed || signalStatus is Declined) {
        // Kotlin 2.0 起,signalStatus 智慧型轉為共同父類 Status
        signalStatus.signal()
        // 在 Kotlin 2.0 之前,需顯式檢查 signalStatus is Status 才能呼叫 signal()
    }
}

另外,當發生例外時,K2 會在控制流上更保守地恢復變數的可為空性:

fun testString() {
    var stringInput: String? = null
    // stringInput 智慧型轉為非空 String
    stringInput = ""
    try {
        // 編譯器知悉 stringInput 非空
        println(stringInput.length)  // 輸出 0
        // 此處丟棄之前的智慧型轉型資訊,將 stringInput 恢復為 String? 型別
        stringInput = null
        // 拋出例外
        throw Exception()
        stringInput = ""
    } catch (e: Exception) {
        // 在 Kotlin 2.0 中,編譯器意識到 stringInput 可能為空,因此需要使用安全呼叫符
        println(stringInput?.length)  // 輸出 null
        // 而在 Kotlin 1.9.20 中,編譯器錯誤地允許直接呼叫 length
    }
}

invokedynamic

Kotlin 2.0.0 起,JVM 上的 lambda 默認可能使用 invokedynamic,會影響到 lambda.toString() 的輸出形式:

fun main() {
    val f = {}
    // Kotlin 2.0.0 起(預設 invokedynamic)可能輸出形如 FileKt$$Lambda$...
    println(f.toString())
}

Kotlin 2.1.0

2.1.0 版本也帶來不少提升程式碼整潔性的特性,同時開始加強對外部註解(如 JSpecify)的整合。

when 表達式:守衛條件(Guard Conditions)

從 2.1.0 起,when 分支支援在分支條件中透過 if 關鍵字加入額外的 Boolean 約束,不再需要嵌套 if-else:

sealed interface Animal {
    data class Cat(val mouseHunter: Boolean) : Animal
    data class Dog(val breed: String) : Animal
}

fun handleAnimal(animal: Animal) {
    when (animal) {
        is Animal.Cat if animal.mouseHunter -> println("捕鼠能手")
        is Animal.Cat -> println("普通的貓")
        is Animal.Dog -> println("狗狗")
    }
}

2.1.0 裡此功能以預覽(preview)形式支援。

非局部的 break 與 continue

Kotlin 2.1.0 引入了非局部的 break/continue(預覽),可以在內聯函式的 lambda 中使用外層迴圈的 break 或 continue,編譯器會在相同私有作用域內自動 smart cast 到 backing field 的實作型別:

fun processList(elements: List<Int>): Boolean {
    for (element in elements) {
        val variable = element.nullableMethod() ?: run {
            println("元素無效,跳過此元素")
            // 非局部 continue:跳過外層 for 迴圈的當前迭代
            continue
        }
        if (variable == 0) return true
    }
    return false
}

這裡示範的情境中,continue 的呼叫發生在 run 內聯 lambda,但能作用於外層的 for 迴圈;在 2.1.0 之前無法實現。

多 $ 字元的字串插值

在處理巢狀模板(例如 JSON 模式或程式碼產生器)時,字串裡的 $ 符號常會產生歧義;2.1.0 引入了多重 $ 符號語法,透過指定 $ 的數量來決定插值的深度:

// 只有使用兩個 $$ 開頭的表達式才會被插值
val template = $$$"""
    {
        "id": "$$id",
        "value": "$value"
    }
"""

(語法為示意,實際使用請參考官方語法說明。)

JSpecify 註解的嚴格模式

為了提升與 Java 的互操作安全性,Kotlin 2.1.0 將 JSpecify 註解(例如 @Nullable 與 @NonNull)的預設處理從警告升級為錯誤,也就是在呼叫標記了 JSpecify 的 Java 程式時,型別不匹配會直接導致編譯失敗,而非執行時的 NPE。

Java 範例:

import org.jspecify.annotations.*;

public class SomeJavaClass {
  @NonNull public String foo() { return "x"; }
  @Nullable public String bar() { return null; }
}

Kotlin 範例:

fun test(sjc: SomeJavaClass) {
    sjc.foo().length
    // Kotlin 2.1.0 起:bar() 的返回值若直接存取成員會報錯
    // sjc.bar().length
    println(sjc.bar()?.length)
}

Kotlin 2.2.0

Kotlin 2.2.0 開始增加了上下文參數(context parameters)與嵌套型別別名等特性,主要是為了減少樣板程式碼同時增強封裝。

上下文參數(Context Parameters)

作為實驗性 Context Receivers 的後繼,上下文參數提供了一種更清晰的方式來宣告函式的隱式依賴。透過 context(name: Type) 語法,函式可以存取上下文中的成員,且必須透過名稱引用,避免接收器歧義:

context(tx: TransactionContext)
fun saveUser(user: User) {
    tx.execute("INSERT INTO users...")
}

此特性在 2.3.20 的上下文參數重載決策規則有進一步規範,確保與普通參數在重載選擇時能有一致的優先級。

嵌套型別別名(Nested Typealiases)

2.2.0 開始支援在類別內定義型別別名,並且在 2.3.0 進入穩定版。它可以讓開發者在特定領域或類別中使用的別名進行私有化或區域化,避免污染頂層命名空間:

class NetworkManager {
    // 嵌套型別別名,僅在 NetworkManager 或其外部透過限定名稱使用
    private typealias RequestId = String

    fun cancelRequest(id: RequestId) { /* ... */ }
}

common 原子型別

Kotlin 提供了 common 平台的原子類別(atomics):

import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.ExperimentalAtomicApi

@OptIn(ExperimentalAtomicApi::class)
fun main() {
    val counter = AtomicInt(0)
    counter.incrementAndGet()
    println(counter.value)
}

Kotlin 2.3.0

Kotlin 2.3.0 的核心主要圍繞「錯誤防範」,透過引入未使用返回值檢查器(unused return value checker)和顯式底層欄位(explicit backing field),來提升程式碼的安全性。

未使用的返回值檢查器

當函式返回一個非 Unit 且非 Nothing 的值,但在呼叫處被靜默忽略時,編譯器會發出警告。透過編譯器選項可以控制檢查模式:

配置模式 行為描述
-Xreturn-value-checker=check 僅報告標有 @MustUseReturnValues 的函式(包含 stdlib 中多數函式)
-Xreturn-value-checker=full 報告專案中所有具有返回值的函式,除非標有 @IgnorableReturnValue

例如,list.filter { it > 0 } 若沒有把結果指派出去,現在會被編譯器捕捉:

fun formatGreeting(name: String): String {
    if (name.isBlank()) return "Hello, anonymous user!"
    if (!name.contains(' ')) {
        // 建構的字串結果未被使用
        "Hello, " + name.replaceFirstChar { it.titlecaseChar() } + "!"
    }
    val (first, last) = name.split(' ')
    return "Hello, $first! Or should I call you Dr. $last?"
}

上例中某個 if 分支構造了字串卻沒有被賦值或回傳,Kotlin 2.3.0 編譯器會警告結果被忽略。

顯式底層欄位(Explicit Backing Field)

在 Kotlin 中,將可變的內部狀態(例如 MutableStateFlow)暴露為只讀的公共屬性(例如 StateFlow)是常見模式;過去通常需要定義兩個屬性(_state 與 state)。2.3.0 引入了可以直接在屬性中宣告 field 的型別的語法,簡化此類用法:

// 語法改進:直接在屬性內定義 field 的實作型別
val loadingState: StateFlow<Boolean>
    field = MutableStateFlow(false)

fun startLoading() {
    // 在私有作用域內,編譯器會自動將 loadingState 智慧型轉為 MutableStateFlow
    loadingState.value = true
}

Kotlin 2.3.20

2.3.20 作為最近的穩定版本更新,主要引入了基於名稱的解構宣告(name-based destructuring)與智慧化的建構工具整合。

基於名稱的解構宣告(Name-based Destructuring)

傳統的 Kotlin 解構是基於位置(依賴 componentN()),若資料類別屬性順序變動,解構程式碼可能在不報錯的情況下產生順序錯配。2.3.20 引入了顯式名稱匹配模式:

範例:

data class User(val username: String, val email: String)

fun main() {
    val user = User("alice", "[email protected]")

    // 位置解構:順序寫錯會造成「靜默錯配」
    val (email, username) = user
    println(email)     // alice
    println(username)  // [email protected]

    // 2.3.20:name-based destructuring(示例)
    // val (username = u, email = e) = user
    // println(u)
    // println(e)
}

編譯器在 2.3.20 提供三種相關模式:

  • only-syntax:支援新語法
  • name-mismatch:當位置解構與屬性名稱不符時給予警告
  • complete:透過不同語法區分兩種解構方式,並強制完整檢查

context parameters 的變化

context parameters 在重載決議上有更嚴格的規則;當僅以「是否有 context parameters」來區分重載時,可能會導致歧義錯誤:

class Logger { fun info(msg: String) = println("INFO: $msg") }

fun saveUser(id: Int) {
    println("Saving user $id (no logger)")
}

context(logger: Logger)
fun saveUser(id: Int) {
    logger.info("Saving user $id")
}

fun main() {
    val logger = Logger()
    context(logger) {
        // 2.3.20:當僅以「是否有 context parameters」區分重載時,可能變成歧義錯誤
        // saveUser(1)
    }
}

編譯器插件

  • Lombok 插件已進入 Alpha:為支援更多 Java 遺留專案,Lombok 編譯器插件現在支援更複雜的註解組合。
  • JPA 插件優化:自動套用 all-open 預設,確保 @Entity 類在執行時能被正確代理,解決延遲載入中的初始化問題。
  • Maven 智能預設值:在 Maven 專案中,只需開啟 <extensions>true</extensions>,插件會自動註冊原始碼根目錄並加入 standard library 依賴。

Wasm 與 Native

在 2.0 至 2.3.20 的更新中,對非 JVM 的支援也有不少進展,特別是在 WebAssembly(Wasm)與 iOS(Swift)互操作的領域。

Kotlin/Wasm

  • Kotlin 2.2.20 正式宣布 Wasm 進入 Beta 階段。
  • 在效能方面,透過預設啟用 Binaryen 優化(如 --closed-world),生產環境的二進位檔大小在 2.3.0 中進一步縮小約 13%。
  • 另外在 2.1.20 引入了 DWARF 偵錯支援,使專案能在 V8 或 SpiderMonkey 引擎外進行原始碼級別的偵錯。

Kotlin/Native 與 Swift

  • Kotlin/Native 的 Swift Export 正逐步完善,例如 2.3.0 增強了對 Swift 列舉與變長參數(varargs)的直接對映,讓 Swift 開發者在呼叫 Kotlin 程式時感覺更貼近原生 Swift 框架。
  • 安全性也有顯著強化:2.2.20 引入了對 Stack Canaries(堆疊金絲雀 / Stack Protectors)的支援,可有效防禦緩衝區溢位攻擊,開發者可以透過 kotlin.native.binary.stackProtector=yes 開啟。

適配(Deprecated / Removed)

隨著版本迭代,舊的 Kotlin 功能逐步被移除,例如:2.1.0 徹底移除了對語言版本 1.4 與 1.5 的支援,同時在 2.3.0 也將 1.8 標記為錯誤。

棄用元件 版本階段 替代方案 / 影響
kotlin-android-extensions 2.1.20 (Error) 使用 ViewBinding 或 Jetpack Compose
kotlinOptions DSL 2.2.0 (Deprecated) 遷移到 compilerOptions 區塊
Number.toChar() 2.3.0 (Error) 使用明確的 Int.toChar() 或其他轉換
androidTarget (KMP) 2.3.0 (Warning) 遷移至新的 Google KMP Android 插件

版本變化總覽

以下為各版本的重點變化摘要(節錄):

版本 日期 類型 主要調整 / 摘要 Breaking changes / 注意事項
2.0.0 2024-05-21 Language K2 編譯器全平台穩定且預設;JVM lambda 預設 invokedynamic;KMP 編譯期嚴格隔離 common/platform source sets;Gradle:Compose compiler 進入 Kotlin 倉庫並提供新插件 大量不相容 / 棄用條目集中在 compatibility guide(語義修正與 K2 行為差異、Gradle 屬性/插件 ID 移除等)
2.0.1 2024-08-06 Bug fix 修復為主(Xcode/編譯器穩定性等) 可能導致更嚴格封裝的修復(例如 +=/-= 不能再繞過 private setter)
2.0.2 2024-08-22 Tooling data class copy() 可見性一致化遷移(warning + 註解/編譯選項);context parameters 取代 context receivers 的遷移;K/N:GC 並發標記;Wasm、Stdlib、Gradle 等多項遷移開始發警告 / 升格為錯誤 context receivers 警告、Wasm default import 變 error、一些 bitcode/Embed 參數移除等
2.1.0 2024-11-27 Language 預覽:guard conditions、非局部 break/continue、多 $ 插值;JSpecify 嚴格 nullability;KMP:Swift export 基礎支援、compilerOptions DSL 穩定 language version 1.4/1.5 移除;stdlib 多項舊 API 由 warning→error
2.1.x 2025-01 ~ 2025-05 Bug fix / Tooling K2 kapt 預設啟用;KMP 新 executable{} DSL;Stdlib:common atomics、UUID 改進;Compose 編譯器限制放寬 移除 withJava()、kotlin-android-extensions 直變錯誤等
2.2.0 2025-06-23 Language context parameters 預覽;多項 2.1 預覽特性轉穩定;JVM default method 生成規則變更、-jvm-default 穩定;Stdlib Base64/HexFormat 穩定 -language-version=1.6/1.7 不再支援;Ant 弃用;kotlinOptions{} 區塊升為 error
2.2.x 2025-08 ~ 2025-10 Tooling / Bug fix Kotlin/Wasm 升級 Beta;Swift export 預設;JS Long→BigInt;Native stack canaries、調試顯示強化;Gradle 增量編譯/發佈強化 多項 Gradle API/DSL 弃用節奏加速
2.3.0 2025-12-16 Language nested typealias、when 資料流完備性檢查;預設啟用顯式返回型別表達式體中的 return;新增 unused return value checker;Wasm 預設啟用 FQN;JS suspend 導出(實驗)等 不再支援 -language-version=1.8(以及非 JVM 無 1.9);AGP 9 / kotlin-android 插件遷移強制診斷;Ant 支援移除
2.3.1 ~ 2.3.20 2026-02 ~ 2026-03 Bug fix / Tooling 修復 kotlinx.serialization 插件競爭條件;Maven 簡化配置進入穩定;name-based destructuring、context parameters overload resolution 規則變更;Stdlib 與 Native 的多項改善 2.3.x 中新增多項 Gradle 屬性棄用(例如 kotlin.publishJvmEnvironmentAttribute、isolated-projects 相關屬性等)

最後

總結一下,從 Kotlin 2.0 到 2.3 的主要變化包括:

  • K2 時代的開始(編譯器重寫與效能大幅提升)
  • 引入與穩定化多項新語法(guard conditions、非局部 break/continue、多 $ 插值、context parameters、when 的資料流完備性檢查等)
  • 工具鏈的「強約束 + 自動化」提升(更多編譯器檢查、Maven/Gradle 整合改進)
  • Native 與 Wasm 場景顯著推進(Wasm Beta、Swift Export 改善、Native 的安全性增強)

可以看出,現在的 Kotlin 與兩年前已經有很大差異;如果你想要更好的開發體驗,升級 Kotlin 版本很可能是必要的。但升級成本也越來越高,因為 Kotlin 的生態(如 Room、Compose、AGP 等)彼此綁定緊密,往往會帶來較大的相容性調整。


原文出處:https://juejin.cn/post/7618764794955628607


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬10   ❤️2
460
🥈
我愛JS
📝2   💬9   ❤️2
83
🥉
💬1  
4
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付