Kotlin 2.3.0 带来了稳定的时间 API、显式幕后字段、改进的 Swift 互操作性以及更完善的工具链。
下面我们来一起浏览那些对于 Android 开发者需要关注的新特性。
稳定的时间 API:Clock 与 Instant
kotlin.time.Clock 和 kotlin.time.Instant API 现已正式稳定,无需再添加 @OptIn 注解即可使用。
import kotlin.time.*
// 获取当前时间
val now: Instant = Clock.System.now()
// 计算时间差
val eventTime = Instant.parse("2025-12-25T10:00:00Z")
val timeUntilEvent: Duration = eventTime - now
println("距离活动开始还有:${timeUntilEvent.inWholeHours} 小时")
// 通过时间分量创建 Instant
val deadline = Instant.fromEpochSeconds(1735084800)
// 比较时间
if (now > deadline) {
println("截止时间已过!")
}
Android 场景建议:用它替代 System.currentTimeMillis(),实现更简洁、类型安全的时间处理。
class SessionManager {
private var sessionStart: Instant? = null
fun startSession() {
sessionStart = Clock.System.now()
}
fun getSessionDuration(): Duration {
val start = sessionStart ?: return Duration.ZERO
return Clock.System.now() - start
}
}
显式幕后字段(实验性)
我终于可以摆脱命名两个变量的困局了!
你可以定义与暴露属性类型不同的幕后字段,无需再单独声明私有属性来实现这一点:
@OptIn(ExperimentalBackingFields::class)
class UserPreferences {
// 幕后字段是可变列表,但对外暴露为不可变列表
val recentSearches: List<String>
field = mutableListOf()
fun addSearch(query: String) {
recentSearches.add(query) // 直接修改幕后字段
if (recentSearches.size > 10) {
recentSearches.removeAt(0)
}
}
}
// 使用示例
val prefs = UserPreferences()
prefs.addSearch("kotlin 2.3")
prefs.addSearch("coroutines")
// 外头是无法使用幕后字段的
// prefs.recentSearches.add("flow")
println(prefs.recentSearches) // [kotlin 2.3, coroutines]
Kotlin 2.3 之前你需要这样做(我确实对这个感到厌烦):
class UserPreferences {
private val _recentSearches = mutableListOf<String>()
val recentSearches: List<String> get() = _recentSearches.toList()
fun addSearch(query: String) {
_recentSearches.add(query)
}
}
如果你对实现方法感到好奇:
其实就是增加了一个 private 的字段用于类的内部使用。
当然,作为实验性功能,你需要添加额外的编译条件:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xexplicit-backing-fields")
}
}
未使用返回值检查器(实验性)
当你忘记使用某个接口的返回值时,该功能可以帮你捕获这类不易察觉的潜在 Bug。
// 有潜在问题的用法
@OptIn(ExperimentalUnusedReturnValueChecker::class)
fun badprocess() {
val list = mutableListOf(1, 2, 3)
// 警告:'remove' 的返回值未被使用
list.remove(2) // 你是否需要检查删除是否成功?
// 警告:'plus' 的返回值未被使用
list.plus(4) // Bug!此操作不会修改原列表,而是返回一个新列表
// ...
}
// 正确用法
fun goodprocess() {
val removed = list.remove(2)
if (!removed) println("元素未找到")
val newList = list + 4 // 使用返回的新列表
}
当一个表达式返回的不是 Unit 或 Nothing 类型的值,且没有被传递给其他函数、用于条件判断或以其他方式使用时,它就会发出警告。
该功能会帮助开发者检查可能存在的 Bug:
| 表达式 | Bug | 修复方案 |
|---|---|---|
list.plus(item) | 返回新列表,不会修改原列表 | list = list + item 或 list.add(item) |
string.replace("a", "b") | 返回新字符串,不会修改原字符串 | val result = string.replace(...) |
map.minus(key) | 返回新映射,不会修改原映射 | map = map - key |
额外的编译条件:
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xreturn-value-checker=check")
}
}
增强的 UUID 支持(实验性)
原生支持生成 v4(随机)和 v7(基于时间)类型的 UUID。
import kotlin.uuid.*
@OptIn(ExperimentalUuidApi::class)
fun generateIds() {
// 随机 UUID(v4)
val randomId = Uuid.random()
println(randomId) // 示例:550e8400-e29b-41d4-a716-446655440000
// 基于时间的 UUID(v7)- 可按创建时间排序
val timeBasedId = Uuid.randomV7()
// 基于特定时间戳生成 v7 UUID
val instant = Clock.System.now()
val historicalId = Uuid.randomV7(instant)
// 安全解析(无效时返回 null 而非抛出异常)
val parsed: Uuid? = Uuid.parseOrNull("invalid-uuid")
// 按时间顺序比较 v7 UUID
val id1 = Uuid.randomV7()
delay(100)
val id2 = Uuid.randomV7()
println(id1 < id2) // true - v7 UUID 支持按时间排序
}
下面提供一个速查表帮助你如何选择合适的 UUID 版本:
| UUID 版本 | 使用场景 |
|---|---|
v4 (random()) | 通用唯一 ID,无需排序的场景 |
v7 (randomV7()) | 数据库主键(可排序)、事件 ID 等需要按创建时间排序的场景 |
额外的编译条件:
kotlin {
compilerOptions {
freeCompilerArgs.add("-opt-in=kotlin.uuid.ExperimentalUuidApi")
}
}
表达式体函数中支持 return
在具有显式返回类型的单表达式函数中,可以使用 return 语句。
// userId 如果为空,函数返回 "default"
fun getDisplayNameOrDefault(userId: String?): String = getDisplayName(userId ?: return "default")
// 复杂校验场景
fun validateEmail(email: String): ValidationResult =
when {
email.isBlank() -> return ValidationResult.Error("Email required")
!email.contains("@") -> return ValidationResult.Error("Invalid format")
else -> ValidationResult.Success
}
嵌套类型别名
现在类型别名可以引用其他类型别名。
// 基础类型别名
typealias UserId = String
typealias ProductId = String
// 嵌套类型别名
typealias UserProducts = Map<UserId, List<ProductId>>
typealias UserProductsCallback = (UserProducts) -> Unit
// 使用案例
class ProductRepository {
fun getUserProducts(callback: UserProductsCallback) {
val products: UserProducts = mapOf(
"user_1" to listOf("prod_a", "prod_b"),
"user_2" to listOf("prod_c")
)
callback(products)
}
}
改进的 Swift 互操作性
Kotlin 枚举现在会导出为原生 Swift 枚举,并且可变参数(vararg)函数可以正常工作。
// Kotlin 代码
enum class PaymentStatus {
PENDING, COMPLETED, FAILED, REFUNDED
}
fun processPayments(vararg payments: Payment): List<Receipt> {
return payments.map { process(it) }
}
// Swift 代码 - 现在可以自然调用
let status: PaymentStatus = .completed
switch status {
case .pending: print("Waiting...")
case .completed: print("Done!")
case .failed: print("Error")
case .refunded: print("Money back")
}
// 可变参数也能直接使用
let receipts = processPayments(payment1, payment2, payment3)
Compose 编译器:Release 版本支持清晰堆栈追踪
借助反混淆的堆栈信息,你可以在生产环境中调试 Compose 的崩溃问题。
// build.gradle.kts
composeCompiler {
// 启用组键格式的堆栈追踪(2.3 版本默认开启)
includeSourceInformation = true
}
优化前:Release 包中的崩溃日志是混淆后的,难以定位问题。
优化后:生成清晰的 Compose 组键,可通过 R8 映射文件关联到你的源代码,大幅提升调试效率。
Gradle 与 AGP 更新
我现在看到 AGP 的更新,只想喊田文镜!
版本要求:
| 工具 | 最低版本 | 最高版本 |
|---|---|---|
| Gradle | 7.6.3 | 9.0.0 |
| AGP(Android Gradle 插件) | 8.2.2 | 8.13.0 |
| Java | 8 | 25 |
AGP 9.0+ 的一个重要变更:
// 旧写法 - 在 AGP 9.0+ 中不再生效
plugins {
id("com.android.application")
id("kotlin-android") // 需要移除这一行!
}
// 新写法 - kotlin-android 已内置到 AGP 9.0+
plugins {
id("com.android.application")
// Kotlin 支持已自动启用
}
数据流的穷举 when 表达式
更智能的穷举检查,能够理解你的代码执行流程。
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
data object Loading : Result<Nothing>()
}
fun handleResult(result: Result<String>) {
// 编译器能识别所有分支已被覆盖
when (result) {
is Result.Success -> println(result.data)
is Result.Error -> println(result.message)
Result.Loading -> println("Loading...")
// 无需写 'else' 分支 — 编译器会验证穷举性
}
}
// 具备数据流感知能力
fun process(result: Result<String>?) {
if (result == null) return
// 编译器知道此处 result 不为 null
when (result) {
is Result.Success -> {}
is Result.Error -> {}
Result.Loading -> {}
}
}
fun getPermissionLevel(role: UserRole): Int {
// 覆盖了 when 表达式之外的 Admin 场景
if (role == UserRole.ADMIN) return 99
return when (role) {
UserRole.MEMBER -> 10
UserRole.GUEST -> 1
// 你不再需要编写这个 else 分支了
// else -> throw IllegalStateException()
}
}
总结
Kotlin 2.3.0 主要致力于稳定实验性特性,并改进多平台互操作性。
其中,稳定的时间 API 和显式幕后字段是对 Android 开发者而言最亮眼的更新。