为什么安卓全面转向 Kotlin

Kotlin 由 JetBrains 开发,2017 年被 Google 宣布为安卓官方首选语言,此后新项目几乎都用它起步。原因不复杂:在完全兼容 Java 生态的前提下,它用更少的代码表达同样的意图,并且把很多运行时错误提前到了编译期。

具体收益可以量化为三点:空指针崩溃大幅减少(空安全是类型系统的一部分);样板代码显著变短(数据类、属性访问器、Lambda);异步代码可读性提升(协程取代回调地狱)。

Kotlin 的价值不在“更简洁”本身,而在“把一整类错误提前到编译期”。写得少只是副产品,编译期就能拦住问题才是核心收益。

如果你还在维护 Java 工程,不必一次性重写:Kotlin 与 Java 可以在同一模块内互调,按功能模块逐步迁移是最现实的路径。

空安全:把崩溃挡在编译期

空指针异常曾是安卓崩溃统计里的头号来源。Kotlin 把可空性写进类型系统:String 表示一定不为空,String? 表示可能为空,编译器强制你在使用前处理。

fun greet(name: String?): String {
    // 安全调用 + Elvis 运算符:为空时给默认值
    val display = name?.trim()?.takeIf { it.isNotEmpty() } ?: "游客"
    return "你好,$display"
}

fun main() {
    println(greet(null))        // 你好,游客
    println(greet("  绿码  "))  // 你好,绿码
    // val len = name.length    // 编译器直接报错:name 可能为空
}

几个高频操作符要记牢:?. 安全调用,?: 提供默认值,!! 强制断言(除非你百分百确定,否则不要用),as? 安全类型转换。工程里可以开启编译告警,把滥用 !! 视为代码异味在评审中拦下来。

数据类、密封类与解构

Kotlin 用几个关键字省掉了大量样板代码,这也是它最直观的收益。

四类常用声明
  • data class自动生成 equals、hashCode、toString、copy,天生适合做数据模型。
  • sealed class子类数量在编译期确定,配合 when 可做穷尽检查,是表达状态机的最佳工具。
  • object声明单例,线程安全且延迟初始化,替代 Java 的双检锁写法。
  • 解构声明用 val (a, b) = pair 拆解 componentN,也能给自定义类扩展 componentN。
Kotlin 入门示意:简洁的机器人头部图标与右上方层叠的蓝青渐变色块,色块以细线连接成语法结构
更少的样板代码:把重复的模板交给语言,把注意力留给业务逻辑。

密封类尤其值得强调。用它定义界面状态(加载中 / 成功 / 失败)后,when 表达式会检查你是否处理了所有分支;将来新增一种状态,编译器会报出所有漏处理的位置。这比用枚举加一堆可选字段可靠得多。

扩展函数与作用域函数

扩展函数允许你不修改原类就为它添加方法,非常适合把工具逻辑收敛到调用点附近。

// 扩展函数:给 String 增加领域语义的方法
fun String.toDisplayPhone(): String =
    if (length == 11) "${substring(0, 3)}****${substring(7)}" else this

// 扩展属性
val String.isChineseMobile: Boolean
    get() = matches(Regex("^1[3-9]\d{9}$"))

fun main() {
    println("13800138000".toDisplayPhone())     // 138****8000
    println("13800138000".isChineseMobile)      // true
}

作用域函数有五个:let、run、with、apply、also。选择方法看两点——你想返回对象本身还是 lambda 结果,以及函数体内用 it 还是 this。

  • apply:配置对象后返回自身,最常用于构建与初始化。
  • let:对非空对象做变换,返回值是 lambda 结果。
  • also:做副作用(打日志、校验)后返回自身。
  • run:需要同时做配置与计算,返回 lambda 结果。

不要为了“写得短”而嵌套多层作用域函数,可读性下降的收益是负的。

协程:结构化并发的异步方案

协程让异步代码看起来像同步代码:用 suspend 标记挂起函数,用 lifecycleScope 或 viewModelScope 绑定生命周期,用 withContext(Dispatchers.IO) 切换线程。相比回调与 RxJava,它更易读也更容易取消。

核心概念有三个:挂起(suspend)不是阻塞线程,而是让出线程去做别的事;结构化并发保证父协程不会在子协程完成前结束;异常会沿协程层级向上传播,用 CoroutineExceptionHandler 或 try/catch 处理。

两个关键实践:一是在界面层使用与生命周期绑定的作用域,避免页面销毁后回调更新已销毁的视图;二是耗时任务要能被取消,涉及 IO 的循环要检查 isActive 或使用 ensureActive()。

Kotlin 与 Java 互操作

互操作是无缝迁移的前提,有几个细节必须掌握,否则会出现难以定位的空指针问题。

  • Java 方法返回的类型被称为平台类型,Kotlin 无法知道它是否可空,编译期不做检查。稳妥做法是在边界处显式声明类型(如 val s: String? = javaObj.getName())并处理空值。
  • 用 @JvmStatic、@JvmField、@JvmOverloads 等注解控制编译产物,方便 Java 侧调用。
  • Kotlin 的默认参数在 Java 中不可见,需要 @JvmOverloads 生成重载。
  • 伴生对象(companion object)在 Java 中通过 Companion 访问,加 @JvmStatic 可直接静态调用。

团队协作时一个实用约定:Java 侧的公开方法都用注解标注可空性(@Nullable / @NonNull),这样 Kotlin 调用方就能得到正确的类型信息,边界处的空安全才有保障。