Kotlin 在 2.0 - 2.3 都更新了什么特性,一口气带你看完这两年 Kotlin 更新

0 阅读7分钟

也许你还在用 Kotlin ,但是你是不是很久没关注过 Kotlin 都有什么更新了?实际上这两年里,自从 K2 开始,Kotlin 发布了不少版本,也增加了很多新的特性,今天我们主要就是汇总聊聊,有哪些是你错过的新支持:

img

2.0 之后的 Kotlin 是全新的场景,因为 K2 不是一个普通的大版本更新,K2 属于是对编译器的完全重写,主要是重写统一了中间表示(IR),也实现了更加高效的语义分析,最重要是提升了编译速度,也对 KMP 场景进行了增强,所以从 K2 时代开始,Kotlin 就进入了一个新的时代。

Kotlin 2.0.0

Kotlin 2.0.0 版本是 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.7s29.7s~94%
增量编译初始化耗时0.126s0.022s~488%
增量编译分析耗时0.581s0.122s~376%

这些性能提升,主要来自 K2 内部数据结构的精简,还有对类型推断算法的优化,从而让编译器能够更智能地跳过不必要的重复检查 ,所以:

如果你现在还觉得编译慢,内存占用高,是不是考虑下还没用 K2 ?

Smart Casts

在 Kotlin 2.0 的版本里,智能类型转换(Smart Casts)机制也有了全面的增强,主要是解决了 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 不会发生逃逸,可以在内联闭包进行智能转换:

fun indexOfMax(a: IntArray): Int? {
    var maxI: Int? = null
    a.forEachIndexed { i, value ->
        // K2:能够识别出 maxI 在内联循环中的状态
        if (maxI == null || a[maxI] <= value) {
            maxI = i
        }
    }
    return maxI
}

异常增强

K2 能把智能转换信息进一步传递到 catchfinally ,并在逻辑或(||)运算符后,尝试将类型合并为共同的超类,从而允许开发者在更复杂的表达式后安全地访问成员:

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()
    }
}

上面的示例 signalStatus 通过 || 判断后被智能转换为 Status 类型,所以可以直接调用 signal() 方法。

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
    }
}

当异常发生后,变量 stringInput 会可以为空,必须使用安全调用符来访问。

invokedynamic

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 里是作为预览版支持。

非局部 break 与 continue

Kotlin 2.1.0 引入了非局部的 break/continue 预览特性,可以在内联函数的 lambda 中使用所在外层循环的 breakcontinue编译器会在同一私有作用域内自动 smart cast 到 backing field 的实现类型

fun processList(elements: List<Int>): Boolean {
    for (element in elements) {
        val variable = element.nullableMethod() ?: run {
            println("元素无效,跳过此元素")显式 backing field
            continue  // 非局部 continue,跳出外层 for 循环当前迭代
        }
        if (variable == 0) return true
    }
    return false
}

continue 的调用发生在 run 内联 lambda ,但是可以继续外部的 for 循环,这在 Kotlin 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 。

这也是给我的 GSYVideoPlayer 带来很多 issue 的特性:img

import org.jspecify.annotations.*;

public class SomeJavaClass {
  @NonNull public String foo() { return "x"; }
  @Nullable public String bar() { return null; }
}
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 Receivers 的继任者,上下文参数提供了一种更清晰的方式来声明函数的隐式依赖,通过 context(name: Type) 语法,函数可以访问上下文中的成员,并且必须通过名称引用,从而避免了接收器歧义 :

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

这个特性在 2.3.20 的上下文参数的重载决策规则有了进一步规范,确保和普通参数在重载选择时能有一致的优先级 。

嵌套类型别名

2.2.0 开始支持在类内部定义类型别名,并且这个支持在 2.3.0 进入了稳定版本,它可以让开发者在特定领域或类中使用的别名进行私有化或局部化,避免了顶层命名空间的污染 :

class NetworkManager {
    // 嵌套类型别名,仅在 NetworkManager 或其外部通过限定名使用
    private typealias RequestId = String
    
    fun cancelRequest(id: RequestId) { /*... */ }
}

common 原子类型

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 的核心主要围绕“错误防范”,通过引入未使用的返回值检查器和显式底层字段,来提升了代码的安全性。

未使用的返回值检查器

这是一个很有意思的新特性,当函数返回一个非 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 编译器会警告结果被忽略。

显式底层字段

在 Kotlin 中,将一个可变的内部状态(如 MutableStateFlow)暴露为只读的公共属性(如 StateFlow)是一个极为普遍的场景,而在之前时需要定义两个属性(_statestate),但是 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 作为最近的稳定版本更新,主要是引入了基于名称的解构声明和智能构建工具集成。

基于名称的解构声明

Kotlin 传统的解构是基于位置(依赖 componentN()),如果数据类属性顺序发生变化,解构代码可能会在不报错的情况下产生运行时错误,而 2.3.20 引入了显式的名称匹配模式 :

解构方式语法示例安全性分析
位置解构 (传统)val (name, age) = person属性重排会导致数据错位
名称解构 (新)(val mail = email, val name = username) = user安全,通过属性名显式绑定

编译器现在提供了三种模式:

  • only-syntax 支持新语法
  • name-mismatch 警告位置解构与属性名不符的情况
  • complete 则通过中括号和圆括号区分两种解构
data class User(val username: String, val email: String)

fun main() {
    val user = User("alice", "alice@example.com")

    // 位置解构:顺序写错会“静默错配”
    val (email, username) = user
    println(email)     // alice
    println(username)  // alice@example.com

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

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>,插件会自动注册源代码根目录并添加标准库依赖

Wasm 和 Native

这里还有其他需要聊的,也就是对非 JVM 的支持,在 2.0 至 2.3.20 的更新里也有这需要场景更新,特别是在 WebAssembly 和 iOS 互操作领域。

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 枚举和变长参数的直接映射,Swift 开发者在调用 Kotlin 代码时,感官上与调用原生 Swift 框架更接近。

同时,安全性也有了显著增强,2.2.20 引入了对 Stack Canaries 的支持,可以有效防御缓冲区溢出攻击,开发者可以通过 kotlin.native.binary.stackProtector=yes 开启 。

适配

当然,随着版本的迭代,老的 Kotlin 版本也在被逐步移除,例如 :2.1.0 彻底移除了对语言版本 1.4 和 1.5 的支持,同时在 2.3.0 也将 1.8 标记为错误 。

弃用组件版本阶段替代方案 / 影响
kotlin-android-extensions2.1.20 (Error)使用 ViewBinding 或 Compose
kotlinOptions DSL2.2.0 (Deprecated)迁移到 compilerOptions
Number.toChar()2.3.0 (Error)使用显式的 Int.toChar() 或 Code 转换
androidTarget (KMP)2.3.0 (Warning)迁移至新的 Google KMP Android 插件

版本变化汇总

版本日期类型关键调整breaking changes
2.0.02024-05-21LanguageK2 编译器全平台 Stable 且默认;JVM lambda 默认 invokedynamic;KMP 编译期严格隔离 common/platform source sets;Gradle:Compose compiler 进入 Kotlin 仓库并提供新插件等大量不兼容/弃用条目集中在 compatibility guide 2.0.x(语义修正与 K2 行为差异、Gradle 属性/插件 ID 移除等)
2.0.102024-08-06Bug fix以修复为主(Xcode/编译器稳定性等)可能导致更严格封装的修复:+=/-= 不能再绕过 private setter
2.0.202024-08-22Toolingdata class copy() 可见性一致化迁移(warning + 注解/编译选项);启动用 context parameters 替代 context receivers 的迁移;K/N:GC 并发标记;Wasm:named exports 迁移加严;Stdlib:common UUID;Gradle 支持到 8.8 等多项迁移开始“发警告/升格”:context receivers 警告、Wasm default import 变 error、bitcode embedding 支持移除并弃用相关参数、kotlin.incremental.useClasspathSnapshot 弃用等。
2.0.212024-10-10Bug fixXcode 16 支持等修复
2.1.02024-11-27Language预览:guard conditions、非局部 break/continue、多$插值;JSpecify 严格 nullability;KMP:Swift export 基础支持、compilerOptions DSL 稳定等language version 1.4/1.5 移除;stdlib 多项旧 API 的“warning→error”(如 locale-sensitive case conversion、appendln);Native 冻结 API 弃用升级等
2.1.102025-01-27Bug fix
2.1.202025-03-20ToolingK2 kapt 默认启用;KMP 新 executable{} DSL 替代 Gradle Application;Stdlib:common atomics、UUID 改进、time tracking;Compose 编译器限制放宽;Isolated Projects 兼容性提升。明确列出 breaking/deprecations:withJava() 退场、kotlin-android-extensions 配置即错误、移除旧 classpath snapshot 属性等。
2.1.212025-05-13Bug fix
2.2.02025-06-23Languagecontext parameters 预览;多项 2.1 预览特性转 Stable;编译器 warnings 统一管理;JVM 接口 default method 生成规则变更与 -jvm-default 稳定;Stdlib Base64/HexFormat 稳定;KGP 引入 BTA 等明确列出:-language-version=1.6/1.7 不再支持;Ant 弃用并计划 2.3 移除;kotlinOptions{} 升级为 error;移除 kotlin-android-extensions;JS DCE 相关 DSL 移除等
2.2.102025-08-14Bug fix
2.2.202025-09-10ToolingKotlin/Wasm 升级 Beta;Swift export 默认;JS:Long→BigInt;Native:stack canaries、调试显示增强、x86_64 Apple targets 退场;Gradle:增量编译/发布任务增强;Maven:默认使用 Kotlin daemon;新增 compiler options schema artifact。引入/加严:Wasm KClass.qualifiedName 诊断默认开启并在未显式启用 FQN 时直接报错;多项 Gradle API/DSL 弃用节奏推进
2.2.212025-10-23Bug fix支持 Xcode 26,并修复 Wasm/JS/Gradle 等
2.3.02025-12-16Languagenested typealias、when 数据流完备性检查;默认启用:显式返回类型表达式体中的 return;新增 unused return value checker;Wasm:默认启用 FQN 并压缩 Latin-1;JS:suspend 导出(实验);Stdlib:time tracking 稳定、UUID v4/v7 等;Gradle:注册 generated sources 新 API;Compose:minify 场景 stack trace mapping 输出增强不再支持 -language-version=1.8(以及非 JVM 无 1.9);AGP 9 / kotlin-android 插件迁移强制诊断;Ant 支持移除等
2.3.102026-02-05Bug fix修复包括 kotlinx.serialization 插件相关竞争条件,并包含多项编译器/Compose/Gradle 修复对 2.3.0 的问题进行修复与部分回退
2.3.202026-03-16ToolingMaven “简化配置”进入 Stable(extensions 自动配置 source roots + stdlib);name-based destructuring、context parameters overload resolution 规则变化;Stdlib:Map.Entry.copy();JVM:识别更多 nullability/只读集合注解;Gradle:Kotlin/JVM 编译默认走 BTA;Native:新互操作模式/跨编译检查等2.3.20 在 compat 2.3.x 中新增多项 Gradle 属性弃用(kotlin.publishJvmEnvironmentAttribute`、isolated-projects 相关属性等)

最后

总结一下,从 Kotlin 2.0 到 2.3 版本,主要有几个大变化:

  • K2 时代的开始
  • 全新语法入,如 guard conditions、非局部 break/continue、多$插值、 context parameters、when 的数据流完备检查等
  • 工具链的“强约束 + 自动化”的增强
  • Native 和 Wasm 场景推进

可以看出来,现在的 Kotlin 和两年前已经有了很大的变化,如果你想要更好的体验,升级 Kotlin 版本是必不可少的,当然,升级的成本也越来越高,因为 Kotlin 的捆绑和配套也越来越密切,比如最新的 Room 也是一个较大的破坏性适配更新。

那么,你现在用的是什么 Kotlin 版本?