Kotlin 命令速查表 - Kotlin 常用语法与协程编程大全

这套速查给写 Kotlin 的开发者——无论是 Android 应用、JVM 后端还是随手写脚本——聚焦每天都碰的几件事:用 kotlinc/Gradle 编译运行、空安全那套写法(安全调用、Elvis、非空断言)、filter/map/groupBy 等集合操作符,以及协程做结构化并发。与泛泛的语法罗列不同,条目按"这句代码解决什么问题"组织,从"避免空指针"到"把 I/O 挪出主线程"都有对应。读完应能写出没有散落 !! 的空安全链、流式处理集合,并让协程按作用域正确取消。

编程语言·共 47 条命令·最后更新 2026-07-21
kotlingradlejvm

典型使用场景

面向用 Kotlin 写服务端、Android 或跨平台代码、被编译器空安全与集合 API 折磨过的开发者。表内把四块高频能力并排放:构建层用 ./gradlew build / test / dependencies 保证本地与 CI 结果一致,用 kotlinc -include-runtime -d 打出可直接 java -jar 运行的产物;语言层的空安全优先用 ?. 与 ?: 组合,!! 只在类型已保证非空时才用;数据处理层依赖 filter / map / flatMap / groupBy / partition 这套声明式集合链;并发层用 launch 与 async 的返回值差异区分「只要跑」和「要结果」,withContext(Dispatchers.IO) 把阻塞调用挪出主线程。对象建模则由 data class、sealed class、object、companion object 配合作用域函数(apply 初始化、let 转换、also 副作用)完成。

编译与运行 5

kotlinc main.kt -include-runtime -d main.jar
编译为可执行 jar
java -jar main.jar
运行编译后的 jar
kotlinc -script script.kts
运行 Kotlin 脚本
kotlinc-jvm
进入 REPL
kotlin main.kt
直接运行(无需编译)

Gradle 构建 5

./gradlew build
执行完整构建(编译+测试+打包),发布前统一用 gradlew 避免本地环境差异
./gradlew run
运行配置了 application 插件的主类,本地快速起应用入口
./gradlew test
运行 JUnit 等测试套件,失败会让构建失败,可在 CI 里拦截回归
./gradlew clean
清空 build 目录构建产物,配合 build 得到干净的 from-scratch 构建
./gradlew dependencies
打印依赖及其解析版本,排查冲突和升级影响面

空安全 5

var name: String? = null
用 ? 声明可空类型,后续赋值可以置 null;非空断言前先确认真不为空
name?.length
安全调用:name 为 null 时整个表达式返回 null 而不是抛 NPE,是首选写法
name ?: "default"
Elvis 操作符:左侧为 null 时改用右侧默认值,一行搞定兜底
name!!.length
非空断言:强行当非空去访问,若实际为 null 会抛 NPE,需慎用、多数可被 ?. 和 ?: 替代
val len = name?.length ?: 0
组合:安全调用拿不到就用 0 兜底,链式中最常用的 null-safe 表达式

集合操作 10

list.filter { it > 5 }
保留满足条件的元素并返回新列表,原列表不变
list.map { it * 2 }
对每个元素应用变换,返回长度相同的新列表,通常做 DTO 到视图模型转换
list.flatMap { it.split(",") }
每个元素映射到一个集合再拍平为一个列表,常用于一对多展开
list.groupBy { it.category }
按任意键把列表分组为 Map,如按分类聚合同类对象
list.sortedBy { it.age }
按指定字段返回升序新列表,配合 reversed 可做倒序
list.distinct()
按相等性去重并返回新列表,去重排序逻辑由 equals 决定
list.take(5)
取前 5 个元素作为新列表,常用于分页或截断展示
list.drop(3)
丢弃前 3 个元素并返回其余,常与 take 配对实现分页
list.firstOrNull { it.id == 1 }
返回第一个匹配的元素,没有匹配返回 null,比 first() 更安全不抛异常
list.partition { it > 0 }
按条件一次分成两半:成对的 Pair(first=满足, second=不满足)

协程 Coroutine 6

runBlocking { ... }
阻塞式协程(测试用)
launch { ... }
启动协程(不返回值)
async { ... }
启动协程(返回 Deferred)
delay(1000)
非阻塞延迟
withContext(Dispatchers.IO) { ... }
切换上下文
coroutineScope { ... }
创建协程作用域

类与对象 8

data class User(val name: String, val age: Int)
数据类(自动生成 equals/hashCode/toString/copy)
sealed class Result
密封类(受限继承层次,when 分支可穷举)
object Singleton { val x = 1 }
单例对象声明
companion object { val MAX = 100 }
伴生对象(类级静态成员)
class User(val name: String)
主构造函数声明属性
open class Animal; class Dog : Animal()
继承(父类需 open)
interface Drawable { fun draw() }
接口定义
enum class Color { RED, GREEN, BLUE }
枚举类

作用域函数与委托 8

obj.let { it.foo() }
let:it 接收者,返回 lambda 结果
obj.apply { this.x = 1 }
apply:this 接收者,返回对象本身(初始化用)
obj.also { it.log() }
also:it 接收者,返回对象本身(副作用用)
obj.run { compute() }
run:this 接收者,返回 lambda 结果
with(obj) { foo() }
with:this 接收者,返回 lambda 结果
val lazyVal by lazy { init() }
延迟初始化委托(首次访问时计算)
var x by Delegates.observable(0) { _, old, new -> }
可观察属性委托
val v by map
Map 委托(从 map 按键取值)

参数矩阵

参数作用示例
kotlinc main.kt -include-runtime -d main.jar编译为带运行时的可执行 jarkotlinc main.kt -include-runtime -d main.jar
java -jar main.jar运行编译好的 jarjava -jar main.jar
kotlinc -script script.kts直接运行 Kotlin 脚本kotlinc -script demo.kts
kotlin main.kt免编译直接运行源文件kotlin main.kt
kotlinc-jvm进入 REPL 试代码kotlinc-jvm
./gradlew build完整构建:编译 + 测试 + 打包./gradlew build
./gradlew run运行配置了 application 插件的主类./gradlew run
./gradlew test运行测试套件,失败即构建失败./gradlew test
./gradlew clean清空 build 目录产物./gradlew clean
./gradlew dependencies打印依赖树与解析版本./gradlew dependencies
String?声明可空类型,赋值可为 nullvar name: String? = null
name?.length安全调用,左侧为 null 时整体返回 nullval n = name?.length
name ?: "default"Elvis 兜底,左侧为 null 时取右侧val v = name ?: "default"
name?.length ?: 0安全调用 + 兜底的组合表达式val len = name?.length ?: 0
name!!非空断言,为 null 时抛 NPE,应尽量避免val len = name!!.length
filter { }按条件筛选,返回新列表list.filter { it > 5 }
map { }逐元素变换,常用于 DTO 转视图模型list.map { it * 2 }
flatMap { }映射到集合后拍平,用于一对多展开list.flatMap { it.split(",") }
groupBy { }按键分组为 Maplist.groupBy { it.category }
sortedBy { }按字段升序排序list.sortedBy { it.age }
distinct() / take(n) / drop(n)去重 / 取前 n 个 / 丢弃前 n 个list.take(5)
firstOrNull { }取首个匹配项,无匹配返回 nulllist.firstOrNull { it.id == 1 }
partition { }按条件一次分成满足与不满足两组list.partition { it > 0 }
launch { }启动不返回值的协程,返回 Joblaunch { save() }
async { }启动返回值的协程,返回 Deferredval d = async { load() }
runBlocking { }阻塞式运行协程,适合测试与脚本runBlocking { runApp() }
withContext(Dispatchers.IO) { }切换到 IO 上下文执行阻塞调用withContext(Dispatchers.IO) { readFile() }
delay(1000)非阻塞延迟delay(1000)
coroutineScope { }创建结构化并发作用域coroutineScope { launch { } }
data class数据类,自动生成 equals/hashCode/toString/copydata class User(val name: String, val age: Int)
sealed class密封类,配合 when 可穷举分支sealed class Result
object / companion object单例对象声明 / 类级静态成员companion object { val MAX = 100 }
apply { }接收者为 this,返回对象本身,适合初始化obj.apply { this.x = 1 }
let { }接收者为 it,返回 lambda 结果,适合转换obj.let { it.foo() }
also { }接收者为 it,返回对象本身,适合副作用obj.also { it.log() }
run { } / with(obj) { }接收者为 this,返回 lambda 结果obj.run { compute() }
by lazy { }延迟初始化委托,首次访问才计算val lazyVal by lazy { init() }
Delegates.observable可观察属性委托,赋值触发回调var x by Delegates.observable(0) { _, old, n -> }
map 委托从 Map 按键取值,键不存在则抛异常val v by map

易错点与避坑指南

现象运行到某处直接抛 NullPointerException,堆栈指向一行看着没问题的代码

原因该行用了 !! 非空断言,实际值在运行时为 null

处置把 !! 换成 ?. 配合 ?: 兜底,让 null 走明确的降级分支而不是崩溃

现象链式集合操作跑到中途,后面拿到的是 null 却没报错

原因中间某步用了 ?. 安全调用,null 被静默传递到下游

处置在链路末端用 ?: 补默认值,或改用 ?: early return 让空数据尽早短路

现象接口响应慢,界面像卡住了一样

原因在主线程上直接执行了阻塞调用,没有切到 IO 上下文

处置用 withContext(Dispatchers.IO) { ... } 包住阻塞的读写操作

现象启动协程后拿不到结果,await 位置恒为默认值

原因用了 launch 而不是 async,launch 返回 Job 没有结果

处置需要返回值时改用 async,并在协程作用域内 await 其 Deferred

现象本地能编译,CI 上却报依赖版本冲突

原因本地用了本机缓存与本仓库之外的依赖,版本解析结果不同

处置统一用 ./gradlew dependencies 打印解析结果核对,提交锁文件并只通过 ./gradlew build 构建

现象给 sealed class 写 when 时被要求补 else 分支

原因when 的分支没有覆盖全部子类,或判断的不是常量

处置检查是否漏掉某个子类声明,或把条件改成对常量值的比较让编译器能穷举

现象改成 flatMap 后内存占用突然升高

原因链式集合操作每一步都会产生中间集合,大数据量时层层累积

处置减少链长、对大集合改用 asSequence() 惰性处理,或在末端才做去重与排序

排障路径

  1. 1从零验证当前环境能否编译并运行

    ./gradlew clean build

    先清产物再完整构建;若这里失败,再看下面的分项排查,不要跳过 clean。

  2. 2确认空安全写法在编译期就能拦住问题

    val len = name?.length ?: 0

    这条不抛异常也不返回 null;替换掉 !! 之后,编译器的可空告警也会随之减少。

  3. 3定位依赖版本冲突来自哪个传递依赖

    ./gradlew dependencies

    在输出的依赖树里找同一库出现多个版本的位置,按需 exclude 或统一约束版本。

  4. 4让阻塞的 IO 调用不占住主线程

    withContext(Dispatchers.IO) { readFile(path) }

    执行后若耗时明显下降,说明之前确实是在主线程上跑阻塞调用。

  5. 5快速验证某个协程分支的返回值

    runBlocking { val v = async { load() }; println(v.await()) }

    runBlocking 会在测试或脚本里同步等待,便于观察 await 的实际结果。

  6. 6确认数据分组与切分逻辑是否符合预期

    list.partition { it > 0 }

    返回的 Pair 里 first 是满足条件的集合、second 是不满足的,便于定位分类错误。

  7. 7把源码打成可分发产物

    kotlinc main.kt -include-runtime -d main.jar

    成功后会生成 main.jar,用 java -jar main.jar 直接验证运行结果。

命令示例

null 安全链:安全调用 + Elvis 兜底一行搞定

val name: String? = loadName() val len = name?.length ?: 0 println(len)

?. 在 name 为 null 时短路返回 null,?: 再把 null 换成 0。绝大多数 !! 场景都能被这个组合替代,从而消除 NPE 而不是把风险推到别处。

输出

0

firstOrNull 代替 first 避免集合为空时抛异常

val user = list.firstOrNull { it.id == 1 } println(user?.name ?: "anonymous")

first() 在无匹配时抛 NoSuchElementException,而这类「查不到」在分页查询里是正常分支。firstOrNull { } 把它变成可空值,配合 ?: 继续链式处理。

输出

anonymous

把 API 数据拍平成 DTO 列表

val rows = response.body .flatMap { it.items } .map { Item(id = it.id, title = it.name) } .groupBy { it.title.first().uppercase() }

flatMap 展开一对多结构,map 做 DTO 到视图模型的转换,groupBy 按任意键归并。这一串是列表型页面的标准处理链,且全程返回新列表、不改原集合。

输出

{ A=[Item(id=1, title=Alpha)], B=[Item(id=2, title=Beta)] }

withContext 切到 IO 线程执行阻塞调用

suspend fun load(): List<Item> = withContext(Dispatchers.IO) { repository.blockingQuery() }

协程里直接调阻塞接口会占死所在线程,withContext(Dispatchers.IO) 把这段搬到 IO 线程池再切回原上下文。这是把遗留阻塞代码接进协程体系的标准接缝。

先查清依赖解析版本再升级

./gradlew dependencies --configuration releaseRuntimeClasspath

升级前先看解析结果:同一个 + 版本号可能因为传递依赖冲突落到意料之外的版本。确认后再改版本号,能避免「构建突然挂了」且无迹可寻的情况。

常见坑与注意事项

  • !! 非空断言在运行时才失败:类型系统已经放行的路径上,一旦上游真的返回 null 就是线上 NPE,能用 ?. 和 ?: 的位置不要留 !!。
  • build/ 目录里的旧产物会污染增量编译结果,出现「代码明明改了却跑旧逻辑」时先 ./gradlew clean 再判断,不要急着清缓存重装依赖。
  • Kotlin/JVM 的 == 比较的是引用而非内容(与 Java 语义一致),集合去重请用 distinct() 或数据类的 equals,否则会漏判重复项。
  • lateinit / by lazy 推迟的初始化异常会推迟到首次访问才抛出,排查时看到的报错位置与真正出错的位置相差很远,协程里尤其难定位。
  • 必须用 ./gradlew 而非全局 gradle,否则会用本机 JDK 与 Gradle 版本,表现为别人构建通过、自己构建失败这类难复现问题。
  • runBlocking 会阻塞当前线程直到协程结束,在 suspend 函数或已有协程作用域里调用会直接死锁,它只应用在测试和 main 入口。

提示

  • Kotlin 的空安全是编译期检查,!! 能不用就不用。
  • 协程的 launch 和 async 区别:launch 返回 Job,async 返回 Deferred(可 await)。
  • 集合操作链式调用很优雅,但大数据量时注意中间集合的内存开销。
  • data class 自动生成 copy/equals/hashCode/toString,适合纯数据模型。
  • 作用域函数选择:初始化用 apply,转换用 let/let it,副作用用 also。
  • sealed class 搭配 when 使用时编译器会检查分支是否穷举,无需 else。

常见问题

?. 、?: 和 !! 都能"处理 null",实际该怎么选?

优先级明确:?. 是首选,name?.length 在 name 为 null 时整条表达式返回 null,不抛异常;?: 负责给 null 一个默认值,val len = name?.length ?: 0 把两者串起来,是链式调用里最常见的 null-safe 写法;!! 是把 null 当成非空强行解包,一旦实际为 null 就抛 NPE,属于把编译期检查偷换成运行时崩溃。用 !! 的唯一正当理由是你在紧邻的前一行刚做过非空判断,或者该值由不可变构造保证;其余场景都应被 ?. 和 ?: 替代。

协程里 launch 和 async 到底差在哪,什么时候必须用 async?

区别只在返回值:launch 返回 Job,只表示「这个协程启动并结束了」,拿不到结果;async 返回 Deferred,除了等待结束还能拿到结果,用 .await() 取值。规则很直白——协程体里的结果没人需要时用 launch;要拿返回值、并且确实要并发跑多个再一起收集时用 async。调度上两者都默认在当前上下文,需要切到 IO 做阻塞操作时用 withContext(Dispatchers.IO) { ... } 包起来。测试里需要让协程跑完再断言,用 runBlocking { ... } 阻塞等待,别用 delay(1000) 真的睡一秒。

let、apply、also、run 都能把对象"传进 lambda",怎么区分?

分两个维度选:接收者是 it 还是 this,以及返回 lambda 结果还是对象本身。let { it.foo() } 和 with(obj) { foo() } 都返回 lambda 的计算结果,let 用 it 适合链式转换;apply { this.x = 1 } 返回对象本身,接收者是 this,最适合初始化一批属性;also { it.log() } 同样返回原对象但接收者是 it,作用是插入副作用,比如打完日志继续链下去;run { compute() } 是 this 接收者加返回结果,常用于对非空对象连续调用多个成员。记忆口诀:初始化用 apply,转换用 let,副作用用 also。

first() 和 firstOrNull() 只差一个词,列表取值该怎么选?

first() 找不到就抛 NoSuchElementException,firstOrNull { it.id == 1 } 返回 null,所以后者在数据可能不完整的场景更安全,也更适合先判空再处理。配合的还有一批一次性算清的集合操作:groupBy { it.category } 按任意键聚合成 Map,partition { it > 0 } 用一个谓词一次分成两半并返回成对的 Pair(first 是满足的、second 是不满足的),distinct() 按 equals 去重,take(5) 与 drop(3) 配对做分页,sortedBy { it.age } 升序并可 reversed。注意这些都返回新集合、原列表不变,所以链式调用在大数据量上要留意中间集合的内存开销。

sealed class、enum class 和 data class 分别什么时候用?

sealed class Result 定义的是受限继承层次,所有子类必须写在同一个文件里,配合 when 使用时编译器会检查分支是否穷举,因此不需要 else 分支——最适合表示「成功 / 失败 / 加载中」这类互斥状态。enum class Color { RED, GREEN, BLUE } 是固定常量集合,天然自带一组取值。data class User(val name: String, val age: Int) 适合纯数据模型,编译器自动生成 equals、hashCode、toString 和 copy(),改一个字段可以直接 val b = a.copy(age = 30)。若父类要被继承,记得加 open。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们