Skip to content

Kotlin 速查表

现代 JVM 语言,简洁且完全可与 Java 互操作。

01

基础

变量与类型

优先使用 val(不可变)而非 var(可变)。Kotlin 会进行类型推断但允许显式标注。使用 'is' 进行类型检查(自动智能转换)。一切都是对象——Kotlin 语法中没有原始类型。

kotlin
val name = "Alice"   // immutable (preferred)
var age = 30          // mutable
val pi: Double = 3.14159
val isDev: Boolean = true
val nums: List<Int> = listOf(1, 2, 3)
println(name::class)  // class kotlin.String
println(age is Int)   // true

字符串模板

$var 用于简单变量,${expr} 用于表达式。三引号字符串保留换行——使用 trimIndent() 获得整洁格式。字符串模板使字符串构建简洁可读,无需格式化字符串。

kotlin
val name = "Alice"
val age = 30
println("Name: $name, Age: $age")  // simple var
println("Length: ${name.length}")  // expression
println("Upper: ${name.uppercase()}")
val multiline = """
  Hello, $name
  Age: $age
""".trimIndent()

空安全

Kotlin 的空安全:? 标记可空类型,?. 安全调用(为 null 时返回 null),!! 断言非空(为 null 时抛出 NPE),?: elvis(为 null 时的默认值)。这在编译时消除了 NullPointerException——Kotlin 的核心特性。

kotlin
var name: String? = null  // nullable type
println(name?.length)     // null (safe call)
name = "Alice"
println(name!!.length)    // 5 (non-null assertion)
val len: Int = name?.length ?: 0  // elvis operator
// Non-null types can never be null
val s: String = "hi"  // not nullable

类型转换

Kotlin 要求显式类型转换(没有 Java 那样的隐式拓宽)。使用 toInt()/toDouble()/toString() 进行转换。toIntOrNull() 在失败时返回 null——比抛出异常的 toInt() 更安全。始终处理解析中的 null。

kotlin
val n: Int = 42
val d: Double = n.toDouble()
val s: String = n.toString()
val fromStr: Int? = "100".toIntOrNull()
val invalid: Int? = "abc".toIntOrNull()  // null
println(d, s, fromStr, invalid)

类型检查与智能转换

is 检查类型并在该分支中智能转换变量——无需显式转换。!is 是否定形式。使用 as 进行不安全转换(抛出 ClassCastException),as? 进行安全转换(失败时返回 null)。智能转换是 Kotlin 的一大便利。

kotlin
fun describe(x: Any): String =
    when (x) {
        is Int -> "Int: ${x + 1}"  // x smart-cast to Int
        is String -> "String of length ${x.length}"
        is List<*> -> "List with ${x.size} items"
        else -> "Unknown"
    }
println(describe(42))      // Int: 43
println(describe("hello")) // String of length 5
02

字符串

常用字符串方法

Kotlin 字符串拥有标准库提供的丰富扩展函数。大多数返回新字符串(不可变)。使用这些替代手动循环。uppercase() 等函数是区域感知的——使用 uppercase(Locale.ROOT) 获得一致结果。

kotlin
val s = "Hello, World"
println(s.length)          // 12
println(s.uppercase())     // HELLO, WORLD
println(s.lowercase())     // hello, world
println(s.split(", "))     // [Hello, World]
println(s.replace("o", "0"))  // Hell0, W0rld
println(s.reversed())      // dlroW ,olleH
println(s.startsWith("Hello"))  // true

多行与原始字符串

三引号字符串是原始的——无需转义序列(三重双引号除外)。trimMargin('|') 只保留 | 之后的文本以获得整洁缩进。trimIndent() 移除公共空白。非常适合代码中的 SQL、JSON、HTML。

kotlin
val text = """
    |Hello,
    |World!
""".trimMargin()  // | marks line start
println(text)
val json = """{"name": "Alice", "age": 30}"""
println(json)  // no escape needed for quotes
// trimIndent() removes common leading whitespace

字符串构建

joinToString 是用分隔符、前缀、后缀连接集合的地道方式。buildString 提供 StringBuilder 作用域以简洁构建。避免在循环中重复使用 +——使用 joinToString 或 StringBuilder 以提高效率。

kotlin
val parts = listOf("apple", "banana", "cherry")
println(parts.joinToString(", "))  // apple, banana, cherry
println(parts.joinToString(prefix="[", postfix="]"))
val sb = StringBuilder()
for (p in parts) sb.append(p).append(" ")
println(sb.toString().trim())
// buildString for concise building
val result = buildString {
    parts.forEach { append(it).append(";") }
}

字符串转数字

toInt()/toDouble() 在无效输入时抛出 NumberFormatException。toIntOrNull() 返回 null——配合 ?: 获得安全默认值。toBooleanStrict() 只接受 'true'/'false'。对用户输入或不可信数据使用 OrNull 变体。

kotlin
val n = "42".toInt()           // 42 (throws if invalid)
val d = "3.14".toDouble()      // 3.14
val safe = "abc".toIntOrNull() // null
val withDefault = "x".toIntOrNull() ?: 0  // 0
val bool = "true".toBooleanStrict()  // true
println(n, d, safe, withDefault)

正则表达式

Regex 类包装 Java 的 Pattern。find 返回 MatchResult?(第一个匹配),findAll 返回所有。matches 检查完全匹配,containsMatchIn 检查部分匹配。使用三引号字符串编写模式以避免双重转义反斜杠。

kotlin
val email = Regex("[\w.]+@[\w]+\.[a-z]+")
val text = "Contact: [email protected]"
val match = email.find(text)
println(match?.value)  // [email protected]
println(email.matches("[email protected]"))  // true
val replaced = text.replace(Regex("\d+"), "#")
println(email.containsMatchIn(text))  // true
03

数据结构

List

listOf 创建不可变列表,mutableListOf 创建可变列表。列表从零开始索引。使用 first()/last() 获取两端(空时抛出异常),firstOrNull() 更安全。contains 检查成员。除非需要修改,否则优先使用不可变列表。

kotlin
val nums = listOf(1, 2, 3)  // immutable
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)
mutable[0] = 0
println(nums.size)         // 3
println(nums.first())      // 1
println(nums.last())       // 3
println(nums.contains(2))  // true
println(nums.indexOf(2))   // 1

Map

mapOf 创建不可变映射,mutableMapOf 创建可变映射。'to' 中缀创建键值对。[] 访问返回可空的 V?(缺失时为 null)。使用 getOrDefault 或 getValue(缺失时抛出异常)。用解构 (k, v) 迭代。

kotlin
val ages = mapOf("Alice" to 30, "Bob" to 25)
println(ages["Alice"])          // 30
println(ages.getOrDefault("Eve", 0))  // 0
println(ages.containsKey("Alice"))    // true
for ((k, v) in ages) println("$k: $v")
val mutable = mutableMapOf("a" to 1)
mutable["b"] = 2  // add/update

Set

setOf 创建不可变集合,mutableSetOf 创建可变集合。union/intersect/subtract 返回新集合。集合强制唯一性——如果元素已存在,add 返回 false。用于去重和集合运算。LinkedHashSet 保留插入顺序。

kotlin
val a = setOf(1, 2, 3)
val b = setOf(3, 4, 5)
println(a union b)        // [1,2,3,4,5]
println(a intersect b)    // [3]
println(a subtract b)     // [1,2]
println(a.contains(2))    // true
val mutable = mutableSetOf(1, 2)
mutable.add(3)  // true if added

Array 与原始数组

arrayOf 创建 Array<T>(原始类型会装箱)。使用 intArrayOf/doubleArrayOf 创建未装箱的原始数组(性能更好)。Array(size) { init } 用初始化函数创建。数组是可变的;大多数用例优先使用 List。

kotlin
val arr = arrayOf(1, 2, 3)  // Array<Int>
arr[0] = 10
println(arr.size)  // 3
// Specialized arrays for primitives (no boxing)
val ints = intArrayOf(1, 2, 3)
val doubles = doubleArrayOf(1.0, 2.0)
// Array constructor
val squares = Array(5) { it * it }  // [0,1,4,9,16]
println(squares.toList())

Pair 与 Triple

Pair 和 Triple 组合 2-3 个值。'to' 中缀创建 Pair(常用于映射)。用 val (a, b) = pair 解构。对于超过 3 个值或命名字段,使用 data class——它们更清晰、更易维护。

kotlin
val pair = "Alice" to 30  // Pair<String, Int>
println(pair.first)   // Alice
println(pair.second)  // 30
val (name, age) = pair  // destructuring
val triple = Triple(1, "a", 2.0)
println(triple.first, triple.third)
// Useful for returning two values
fun minMax(list: List<Int>): Pair<Int, Int> =
    Pair(list.min(), list.max())
04

控制流

If / Else(表达式)

在 Kotlin 中,if/else 是返回值的表达式。这替代了三元运算符。两个分支必须返回兼容的类型。用于简洁的条件赋值。作为语句(无 else)时返回 Unit。

kotlin
val score = 85
val grade = if (score >= 90) "A"
            else if (score >= 80) "B"
            else if (score >= 70) "C"
            else "F"
println(grade)  // B
// if returns a value—no ternary needed
// Both branches must have compatible types

When(Switch)

when 是 Kotlin 强大的 switch——支持字面量、范围(in)、否定(!in)、多值(逗号)和 is 检查。作为带 sealed 类型的表达式使用时必须穷尽。无主题时,它是更清晰的 if/else 链。

kotlin
val n = 2
val label = when (n) {
    0 -> "zero"
    1, 2, 3 -> "small"
    in 4..10 -> "medium"
    !in 1..100 -> "out of range"
    else -> "large"
}
println(label)  // small
// When without subject = multi-condition if
when {
    n > 0 -> println("positive")
    n < 0 -> println("negative")
    else -> println("zero")
}

For 循环与范围

.. 是包含范围,until 是不包含,downTo 递减,step 设置步长。withIndex() 提供索引迭代。for-in 适用于任何 Iterable。这些范围对计数循环简洁可读。

kotlin
for (i in 0 until 5) print(i)  // 01234 (excludes 5)
for (i in 1..5) print(i)      // 12345 (includes 5)
for (i in 5 downTo 1) print(i)  // 54321
for (i in 1..10 step 2) print(i)  // 13579
val list = listOf("a", "b", "c")
for ((index, value) in list.withIndex()) {
    println("$index: $value")
}

While 与 Do-While

while 先检查,do-while 后检查(至少运行一次)。两者都需要可变状态。优先使用 for 循环或函数式操作(map、filter)以获得不可变性。在计数未知时使用 while 进行条件驱动循环。

kotlin
var count = 0
while (count < 3) {
    println(count)
    count++
}
var j = 0
do {
    println(j)
    j++
} while (j < 3)
// do-while runs at least once
// Prefer for loops when possible

Break 与 Continue(标签)

continue 跳到下一次迭代,break 退出循环。标签(@outer)允许跳出嵌套循环——在循环前用 label@ 定义,用 @label break/continue。谨慎使用;重构为函数通常更干净。

kotlin
for (i in 1..5) {
    if (i == 3) continue  // skip 3
    if (i == 5) break     // stop at 5
    println(i)  // 1, 2, 4
}
// Labeled breaks for nested loops
outer@ for (i in 1..3) {
    for (j in 1..3) {
        if (i == 2 && j == 2) break@outer
        println("$i,$j")
    }
}
05

函数与 Lambda

函数定义

函数使用 fun name(params): ReturnType。单表达式用表达式体(= expr)。支持默认参数和命名参数。Unit 返回类型 = 仅副作用。默认参数减少了对重载的需求。

kotlin
fun add(a: Int, b: Int): Int = a + b  // expression body
fun greet(name: String, greeting: String = "Hello"): String {
    return "$greeting, $name!"
}
println(add(3, 4))           // 7
println(greet("Alice"))      // Hello, Alice!
println(greet("Bob", greeting = "Hi"))  // named arg
fun log(msg: String): Unit = println(msg)  // Unit = void

Lambda 与高阶函数

Lambda:{ params -> body }。'it' 是单参数的简写。将 lambda 传递给 map/filter/reduce 等高阶函数。尾随 lambda 语法:如果最后一个参数是函数,可以放在 () 外。这是地道的 Kotlin 写法。

kotlin
val square: (Int) -> Int = { x -> x * x }
println(square(5))  // 25
val nums = listOf(1, 2, 3)
println(nums.map { it * 2 })      // [2, 4, 6]
println(nums.filter { it > 1 })   // [2, 3]
println(nums.reduce { a, b -> a + b })  // 6
// 'it' is the implicit single parameter

扩展函数

扩展函数为现有类(甚至来自 Java 的类)添加方法,无需继承或修改。它们是静态函数的语法糖。'this' 指向接收者。用于使 API 流畅。在编译时解析(无动态分派)。

kotlin
fun String.shout(): String = this.uppercase() + "!"
fun Int.isEven(): Boolean = this % 2 == 0
println("hello".shout())  // HELLO!
println(4.isEven())       // true
// Extensions don't modify the class—they're resolved statically
// Use to add utility methods to any type

Inline 与 Infix

inline 将函数体复制到调用点(消除 lambda 开销)。用于热路径中的高阶函数。infix 允许不带点/括号的函数调用(a op b)——用于 DSL 和可读操作如 'to'、'in'、'and'。

kotlin
inline fun measure(block: () -> Unit) {
    val start = System.currentTimeMillis()
    block()
    println("Took ${System.currentTimeMillis() - start}ms")
}
measure { println("working...") }
// Infix functions for readable calls
infix fun Int.times(str: String): String = str.repeat(this)
println(3 times "ab")  // ababab

Vararg 与展开

vararg 接受可变参数(收集到数组中)。* 将数组展开为 vararg。命名参数可跟在 vararg 后。用于灵活的 API 如 listOf()、printf 风格函数。展开运算符是 Kotlin 中 JS 展开的等价物。

kotlin
fun sum(vararg nums: Int): Int = nums.sum()
println(sum(1, 2, 3, 4))  // 10
// Spread operator to pass array
val arr = intArrayOf(1, 2, 3)
println(sum(*arr))  // 6
// Named/default params can follow vararg
fun greet(vararg names: String, greeting: String = "Hi") =
    names.joinToString { "$greeting, $it!" }
println(greet("A", "B"))
06

类与面向对象

类与构造器

主构造器在类头中。val/var 参数成为属性(带 getter/setter)。没有 val/var 时,它们只是构造器参数(不可访问)。init 块在构造期间运行。Kotlin 自动生成 getter/setter。

kotlin
class Person(val name: String, val age: Int) {
    fun greet(): String = "Hi, I'm $name"
    fun isAdult(): Boolean = age >= 18
}
val p = Person("Alice", 30)
println(p.greet())   // Hi, I'm Alice
println(p.name)      // Alice (val = property)
println(p.isAdult()) // true
// val/var in constructor = auto property

Data Class

data class 自动生成 equals、hashCode、toString、copy 和 componentN(用于解构)。用于数据持有者。必须至少有一个 val/var 参数。copy() 创建修改后的克隆——非常适合不可变更新。用 val (a, b) = point 解构。

kotlin
data class Point(val x: Int, val y: Int)
val p1 = Point(3, 4)
val p2 = Point(3, 4)
println(p1 == p2)        // true (value equality)
println(p1.copy(x = 5))  // Point(x=5, y=4)
println(p1)              // Point(x=3, y=4)
// Auto: equals, hashCode, toString, copy, componentN
val (x, y) = p1  // destructuring via componentN()

Sealed Class

Sealed class 将子类型限制在同一文件/kotlin 模块中。when 表达式是穷尽的(编译器检查所有情况)。用于表示有限状态的 ADT(代数数据类型)。与 when 结合,实现安全的、编译时检查的模式匹配。

kotlin
sealed class Result
data class Success(val value: Int) : Result()
data class Failure(val error: String) : Result()
object Loading : Result()
fun handle(r: Result): String = when (r) {
    is Success -> "Got ${r.value}"
    is Failure -> "Error: ${r.error}"
    Loading -> "Loading..."
}
// when is exhaustive—compiler enforces all cases

Object 与 Companion

object 声明单例(一个实例,懒加载初始化)。类内的 companion object 持有'静态'成员(通过 ClassName.member 访问)。使用 object 作为单例,companion 作为工厂方法和常量。Companion 可以实现接口。

kotlin
object Config {  // singleton
    val version = "1.0"
    fun load() = mapOf("key" to "value")
}
println(Config.version)  // 1.0
class User(val name: String) {
    companion object {
        fun create(name: String) = User(name)
        const val MAX = 100
    }
}
val u = User.create("Alice")  // like static method

继承与接口

类默认是 final 的——使用 'open' 允许继承。'override' 是必需的。接口可以有默认实现。一个类继承一个类但可实现多个接口。使用 abstract 表示部分实现。优先使用组合而非继承。

kotlin
open class Animal(val name: String) {
    open fun speak() = "..."
}
class Dog(name: String) : Animal(name) {
    override fun speak() = "Woof"
}
interface Greetable {
    val name: String
    fun greet(): String  // can have default impl
}
class Person(override val name: String) : Greetable {
    override fun greet() = "Hi, $name"
}
07

空安全与作用域函数

安全调用与 Elvis

?. 安全调用(接收者为 null 时返回 null),?: elvis(提供默认值)。链式 ?. 进行深层访问(user?.address?.city)。使用 ?: return/throw 提前退出。这些使 null 处理简洁安全——无需显式 null 检查。

kotlin
var name: String? = "Alice"
println(name?.length)      // 5
name = null
println(name?.length)      // null
println(name?.length ?: 0) // 0 (elvis: default if null)
println(name?.length ?: return)  // early return
// Safe call chain
val city: String? = user?.address?.city

let(空检查)

let 仅在值非空时执行块。'it' 是块内的非空值。用于 null 守卫操作而非 if-null 检查。常见模式:value?.let { ... } 进行安全处理。返回块的结果。

kotlin
var name: String? = "Alice"
name?.let {
    println("Name is $it")  // only runs if not null
    println("Length: ${it.length}")  // it is non-null
}
// Common for null-guarded blocks
val result = name?.let {
    process(it)  // it: String (non-null)
} ?: "default"

apply 与 also

apply 配置对象(返回对象,'this' 接收者)——非常适合构建器。also 执行副作用(返回对象,'it' 参数)——适合链中的日志/调试。两者都返回原始对象,支持流畅链式调用。

kotlin
val list = mutableListOf<Int>().apply {
    add(1); add(2); add(3)  // 'this' = the list
    println("Built $size items")
}
val config = Config().also {
    it.timeout = 30  // 'it' = config
    println("Configured")
}
// apply: configure and return object (this)
// also: side effects, return object (it)

run 与 with

run 在对象上执行块(返回块结果,'this' 接收者)——用于转换对象。with 类似 run 但将对象作为参数(不可链式用于可空类型)。用于对同一对象分组操作。

kotlin
val result = "Hello".run {
    length  // 'this' = string, returns last expr
}  // 5
val r2 = with(StringBuilder()) {
    append("a"); append("b")
    toString()  // returns this
}
// run: object.run { } - returns block result
// with: with(obj) { } - returns block result (not chainable)

TakeIf 与 TakeUnless

takeIf 在谓词为真时返回对象,否则返回 null。takeUnless 相反。用于链中的条件过滤——避免单独的 if 检查。与 ?: 结合获得默认值。非常适合验证管道。

kotlin
val age = 25
val valid = age.takeIf { it >= 18 }  // 25 (if true)
val invalid = age.takeIf { it < 18 }  // null (if false)
val adult = age.takeUnless { it < 18 }  // 25
// Useful for filtering in chains
val email = getUser()?.email?.takeIf { it.contains("@") }
println(valid, invalid, adult)
08

集合与函数式

Map / Filter / Fold

map 转换,filter 选择,reduce/fold 聚合。'it' 是隐式元素。fold 接受种子;reduce 不接受(空时抛出异常)。这些是函数式集合处理的核心——使用替代循环以获得清晰度。

kotlin
val nums = listOf(1, 2, 3, 4, 5)
println(nums.map { it * 2 })          // [2,4,6,8,10]
println(nums.filter { it % 2 == 0 })  // [2,4]
println(nums.reduce { a, b -> a + b }) // 15
println(nums.fold(0) { a, b -> a + b }) // 15
println(nums.sum())                   // 15
println(nums.joinToString(", "))      // 1, 2, 3, 4, 5

FlatMap 与 GroupBy

flatten 移除一层嵌套。flatMap 一步完成映射和扁平化——对嵌套转换至关重要。groupBy 按键分区为 Map。这些对数据处理和分析管道非常强大。

kotlin
val nested = listOf(listOf(1, 2), listOf(3, 4))
println(nested.flatten())         // [1,2,3,4]
println(nested.flatMap { it.map { n -> n * 2 } })  // [2,4,6,8]
val words = listOf("apple", "bat", "ant")
val byFirst = words.groupBy { it.first() }
// {a=[apple, ant], b=[bat]}
println(byFirst)

排序

sorted/sortedDescending 自然排序。sortedBy/sortedByDescending 按键选择器排序。这些返回新列表(不可变)。对于可变列表,使用 sort/sortBy(就地)。使用键选择器按特定字段排序。

kotlin
val nums = listOf(3, 1, 4, 1, 5)
println(nums.sorted())           // [1,1,3,4,5]
println(nums.sortedDescending()) // [5,4,3,1,1]
val people = listOf("Alice" to 30, "Bob" to 25)
val byAge = people.sortedBy { it.second }  // by age
println(byAge)
val byNameDesc = people.sortedByDescending { it.first }
println(byNameDesc)

Sequence(惰性)

Sequence 是惰性的——操作延迟到终端操作(toList、sum、count)执行。避免中间集合,对大数据性能更好。对大型集合上的多步管道使用 asSequence()。类似 Java Stream。

kotlin
val nums = (1..1000000).toList()
// Eager: creates intermediate lists
val eager = nums.filter { it % 2 == 0 }.map { it * 2 }.take(5)
// Lazy: no intermediate collections
val lazy = nums.asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .take(5)
    .toList()  // forces evaluation
println(lazy)  // [4, 8, 12, 16, 20]

Partition 与 Chunked

partition 按谓词拆分为两个列表(返回 Pair)。chunked 分成固定大小的列表。windowed 创建滑动窗口。这些用于批处理、分页和滑动窗口算法。都返回新集合。

kotlin
val nums = listOf(1, 2, 3, 4, 5)
val (evens, odds) = nums.partition { it % 2 == 0 }
println(evens)  // [2, 4]
println(odds)   // [1, 3, 5]
val chunked = nums.chunked(2)
println(chunked)  // [[1, 2], [3, 4], [5]]
val windowed = nums.windowed(3)
println(windowed)  // [[1,2,3], [2,3,4], [3,4,5]]
09

协程与异步

Launch(即发即忘)

launch 启动一个不返回结果的协程(即发即忘)。delay 是非阻塞的(不同于 Thread.sleep)。runBlocking 桥接同步/异步代码(在 main/测试中使用)。协程是轻量级的——数千个可在少数线程上运行。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    launch {
        delay(1000)
        println("World!")  // after 1s
    }
    println("Hello")  // immediately
    // launch doesn't return a result
    // Use for side-effect coroutines
}
// Output: Hello, then World! after 1s

Async 与 Await

async 启动一个返回 Deferred<T> 的协程。await() 挂起直到结果就绪。在 await 之前启动多个 async 以实现并行。用于产生结果的并发计算。类似其他语言中的 Promise/Future。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    val deferred = async {
        delay(1000)
        42  // return value
    }
    val result = deferred.await()
    println(result)  // 42
    // Parallel execution
    val a = async { computeA() }
    val b = async { computeB() }
    println(a.await() + b.await())  // concurrent
}

挂起函数

suspend 函数可以暂停和恢复而不阻塞线程。它们只能从协程或其他 suspend 函数调用。coroutineScope 提供结构化作用域(等待所有子级)。用于异步 API——使异步代码看起来像同步的。

kotlin
import kotlinx.coroutines.*
suspend fun fetchUser(id: Int): String {
    delay(500)  // simulates network
    return "User $id"
}
suspend fun fetchAll(): List<String> = coroutineScope {
    val a = async { fetchUser(1) }
    val b = async { fetchUser(2) }
    listOf(a.await(), b.await())
}
// suspend functions can only be called from coroutines

Flow(冷流)

Flow 是 Kotlin 的冷异步流(类似 RxJava Observable)。值在收集时产生。使用 map/filter/reduce 运算符。emit 生产,collect 消费。非常适合流数据、事件或分页 API。热流使用 SharedFlow/StateFlow。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun numbers(): Flow<Int> = flow {
    for (i in 1..5) {
        delay(100)
        emit(i)  // produce value
    }
}
fun main() = runBlocking {
    numbers().collect { println(it) }  // 1,2,3,4,5
    // Transform
    numbers().map { it * 2 }.filter { it > 4 }.collect {
        println(it)  // 6, 8, 10
    }
}

调度器与上下文

Dispatchers 选择线程池:Main 用于 UI,IO 用于网络/文件(大池),Default 用于 CPU 工作(核心数线程)。withContext 在协程内切换上下文。使用正确的调度器以避免阻塞 UI 或耗尽线程池。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    launch(Dispatchers.Main) { /* UI thread */ }
    launch(Dispatchers.IO) {
        // Network/file I/O (thread pool)
        val data = fetchData()
    }
    launch(Dispatchers.Default) {
        // CPU-intensive work
        val result = heavyCompute()
    }
    // withContext switches dispatcher
    val data = withContext(Dispatchers.IO) { readFile() }
}
10

错误处理与 I/O

Try / Catch / Finally

try/catch/finally 类似 Java,但 try 是返回值的表达式。无受检异常——都是非受检的。捕获特定异常以进行针对性处理。使用 finally 进行清理。对于预期失败,优先使用 Result 或可空返回。

kotlin
val result = try {
    "abc".toInt()
} catch (e: NumberFormatException) {
    0  // fallback
} finally {
    println("cleanup")
}
println(result)  // 0
// try is an expression—returns a value
// Kotlin doesn't have checked exceptions

Result 类型

Result<T> 包装成功或失败(类似 Scala 的 Try)。runCatching 将异常转换为 Result。getOrNull/getOrElse 用于安全访问。onSuccess/onFailure 用于回调。用于预期错误而非异常——更干净的函数式错误处理。

kotlin
fun parse(s: String): Result<Int> =
    runCatching { s.toInt() }
val r = parse("42")
println(r.getOrNull())    // 42
println(r.getOrElse { 0 }) // 42
r.onSuccess { println("OK: $it") }
    .onFailure { println("Err: ${it.message}") }
val r2 = parse("abc")
println(r2.getOrNull())   // null

自定义异常

自定义异常继承 Exception(或子类)。传递消息用于调试。在通用异常之前捕获特定异常(顺序很重要)。对真正异常的情况使用异常;对于预期失败,优先使用 Result 或可空返回。

kotlin
class InvalidAgeException(message: String) : Exception(message)
fun setAge(age: Int) {
    if (age < 0) throw InvalidAgeException("Age cannot be negative: $age")
}
try {
    setAge(-5)
} catch (e: InvalidAgeException) {
    println("Caught: ${e.message}")
} catch (e: Exception) {
    println("Other: ${e.message}")
}

文件 I/O

Kotlin 使用 Java 的 File 加扩展函数。writeText/readText 用于简单情况。useLines 流式处理行(自动关闭,内存高效)。对于大文件,使用 bufferedReader()。始终关闭资源——使用 use { } 块自动关闭。

kotlin
import java.io.File
// Write
File("test.txt").writeText("Hello, File!")
// Read
val content = File("test.txt").readText()
println(content)  // Hello, File!
// Append
File("log.txt").appendText("new line\n")
// Line by line
File("test.txt").useLines { lines ->
    lines.forEach { println(it) }
}

JSON(kotlinx.serialization)

kotlinx.serialization 是 Kotlin 官方的 JSON 库——编译时安全,无反射。用 @Serializable 注解 data class。encodeToString/decodeFromString 用于 JSON。使用 @SerialName 自定义键名,@Optional 用于默认值。替代方案:Gson/Moshi。

kotlin
import kotlinx.serialization.*
import kotlinx.serialization.json.*
@Serializable
data class User(val name: String, val age: Int)
val user = User("Alice", 30)
val json = Json.encodeToString(user)
println(json)  // {"name":"Alice","age":30}
val decoded = Json.decodeFromString<User>(json)
println(decoded.name)  // Alice
// Requires @Serializable annotation
11

协程深入

结构化并发

结构化并发将子协程绑定到父作用域——父级在所有子级完成前不会完成,子级失败会取消兄弟。优先使用 coroutineScope 而非 GlobalScope。这防止了协程泄漏并使取消可预测。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    // coroutineScope waits for all children
    coroutineScope {
        launch { delay(1000); println("A") }
        launch { delay(500); println("B") }
    }
    println("Done")  // after both complete
    // If a child fails, parent cancels siblings
    // Never use GlobalScope unless necessary
}

取消与协作式取消

取消是协作式的——协程必须在挂起点(delay、yield、await)检查它。可取消的挂起函数抛出 CancellationException。使用 ensureActive() 或 isActive 检查。非挂起的 CPU 循环不会响应取消,除非你调用 yield() 或检查 isActive。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    val job = launch {
        repeat(10) { i ->
            delay(300)
            yield()  // explicit suspension point
            println("Working $i")
        }
    }
    delay(700)
    job.cancelAndJoin()  // cancels and waits
    println("Cancelled")
}
// delay/yield check for cancellation
// isActive checks cancellation state

异常处理

launch 中未处理的异常会传播到父级(取消兄弟),除非安装了 CoroutineExceptionHandler。async 将异常推迟到 await()。在 await 周围使用 try/catch。处理程序仅对 launch 中未捕获的异常有效——使用 SupervisorJob 隔离故障。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Caught: $e")
    }
    val job = launch(handler) {
        throw RuntimeException("Boom!")
    }
    job.join()
    // async exceptions surface on await()
    val deferred = async { throw IllegalStateException() }
    try { deferred.await() } catch (e: Exception) {
        println("Async failed: $e")
    }
}

监督(supervisorScope)

supervisorScope(和 SupervisorJob)创建一个作用域,其中子级的失败不会取消其兄弟——每个子级独立失败。用于独立操作(如多个独立的 API 调用)。常规 coroutineScope 在首次失败时取消兄弟(快速失败)。

kotlin
import kotlinx.coroutines.*
fun main() = runBlocking {
    // supervisorScope: child failures don't cancel siblings
    supervisorScope {
        launch { delay(100); throw RuntimeException("A fails") }
        launch { delay(200); println("B still runs") }
    }
    // vs coroutineScope: A's failure cancels B
    // Use SupervisorJob for long-lived services
    val scope = CoroutineScope(SupervisorJob())
}

Channel(CSP 风格)

Channel 在协程之间传递值(类似 Go channel)。Rendezvous(容量 0)同步发送者和接收者;缓冲允许排队。满时 send 挂起,空时 receive 挂起。始终 close() 生产者。对于扇出,使用 BroadcastChannel 或 SharedFlow。大多数流式需求优先使用 Flow。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
fun main() = runBlocking {
    val channel = Channel<Int>(3)  // buffered capacity 3
    launch {
        for (i in 1..5) {
            channel.send(i)
            println("Sent $i")
        }
        channel.close()
    }
    // receive() blocks until available
    for (x in channel) println("Got $x")
    // Produce-consume pattern
    // Rendezvous (cap 0) syncs sender/receiver
}
12

Flow 深入

Flow 运算符

Flow 运算符是冷的——仅在收集时执行。map/filter/take 类似 Stream/Sequence。transform 最灵活(每个输入可发射多个值)。flatMap 变体处理嵌套流:concat(顺序)、merge(并发)、latest(取消前一个)。根据顺序/并发需求选择。

kotlin
import kotlinx.coroutines.flow.*
fun nums() = flow {
    for (i in 1..5) emit(i)
}
suspend fun main() {
    nums().map { it * it }           // 1,4,9,16,25
        .filter { it > 5 }          // 9,16,25
        .take(2)                    // 9,16
        .collect { println(it) }
    // transform: emit multiple values
    nums().transform { x ->
        emit(x)
        emit(x * 10)
    }.collect { println(it) }
    // flatMapConcat / flatMapMerge / flatMapLatest
}

Buffer 与并发

buffer 用固定容量队列解耦生产者和消费者——在生产消费速度不同时有用。conflate 只保留最新值(丢弃中间值)用于 UI/状态更新。collectLatest 在新值到达时取消前一个收集器——非常适合搜索即输入。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun events() = flow {
    for (i in 1..3) {
        delay(100); emit(i)
    }
}
fun main() = runBlocking {
    // Without buffer: producer & consumer alternate (slow)
    events().buffer()  // decouple producer/consumer
        .collect { delay(200); println(it) }
    // conflate: drop intermediate values
    events().conflate().collect { println(it) }
    // collectLatest: cancel previous collection
    events().collectLatest { delay(200); println(it) }
}

StateFlow 与 SharedFlow(热流)

StateFlow 是持有单个最新值的热流——用于 UI 状态(替代 LiveData)。它始终有值并进行 conflate。SharedFlow 是热广播流(多个收集器,无需初始值)——用于事件。replay=1 的 SharedFlow 行为类似缓存事件总线。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    // StateFlow: holds one value, stateful
    val state = MutableStateFlow(0)
    launch {
        state.collect { println("State: $it") }
    }
    state.value = 1  // update synchronously
    state.value = 2
    // SharedFlow: broadcasts to multiple collectors
    val events = MutableSharedFlow<String>()
    launch { events.collect { println("A: $it") } }
    launch { events.collect { println("B: $it") } }
    events.emit("Hello")  // both receive
}

Flow 异常处理

catch 处理上游异常并可发射回退值。它只捕获链中它之前的异常。retry/retryWhen 在失败时重新收集流(对网络调用有用)。对于下游异常,在 try/catch 中包装 collect。永远不要在 flow 构建器内部捕获——让它传播。

kotlin
import kotlinx.coroutines.flow.*
fun numbers() = flow {
    emit(1)
    throw RuntimeException("fail")
    emit(2)  // never reached
}
suspend fun main() {
    // catch operator (upstream only)
    numbers().catch { e -> emit(-1) }
        .collect { println(it) }  // 1, -1
    // retry on failure
    numbers().retry(3) { e ->
        println("Retry: $e"); true
    }.collect { println(it) }
    // retryWhen for custom logic
}

flowOn 与上下文

flowOn 为上游流(生产者 + 其上的运算符)切换调度器。当 flow 执行阻塞 I/O 时这很关键——用 flowOn(Dispatchers.IO) 包装。下游(collect)在调用者的上下文上运行。多个 flowOn 调用为每段创建单独的上下文。

kotlin
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun diskFlow() = flow {
    for (i in 1..3) {
        Thread.sleep(100)  // blocking I/O
        emit(i)
    }
}
suspend fun main() {
    // flowOn changes upstream context
    diskFlow().flowOn(Dispatchers.IO)
        .collect { println(it) }
    // Without flowOn, runs on collector's dispatcher
    // flowOn applies to all operators above it
    diskFlow().map { it * 2 }
        .flowOn(Dispatchers.IO)
        .filter { it > 2 }
        .collect { println(it) }
}
13

Sealed Class 与 ADT

Sealed Class 基础

Sealed class 将子类限制为已知集合(同一文件/模块)。编译器知道所有可能的类型,从而实现无需 else 的穷尽 when 表达式。非常适合建模有限状态(Result、UiState、网络响应)。与 data class 结合,它们构成代数数据类型。

kotlin
sealed class Result<out T> {
    data class Success<T>(val value: T) : Result<T>()
    data class Failure(val error: String) : Result<Nothing>()
    object Loading : Result<Nothing>()
}
fun handle(r: Result<Int>) = when (r) {
    is Result.Success -> println("Got ${r.value}")
    is Result.Failure -> println("Error: ${r.error}")
    Result.Loading -> println("Loading...")
}
// All subclasses defined in same file/module

Sealed Interface(Kotlin 1.5+)

Sealed interface(Kotlin 1.5+)将 sealed 扩展到接口,允许一个类实现多个 sealed 类型——比 sealed class 更灵活。适用同文件/同模块限制。用于类型属于多个类别的领域建模。

kotlin
sealed interface Shape {
    fun area(): Double
}
data class Circle(val r: Double) : Shape {
    override fun area() = Math.PI * r * r
}
data class Square(val side: Double) : Shape {
    override fun area() = side * side
}
// Sealed interfaces allow multiple inheritance
sealed interface Clickable { fun click() }
class Button : Shape, Clickable {
    override fun area() = 0.0
    override fun click() = println("Clicked")
}

穷尽 When

作为表达式(非语句)使用时,编译器要求所有 sealed case——无需 else。添加新子类会在未处理的 when 处产生编译错误,使重构安全。这是 sealed class 用于状态机和 UI 渲染的杀手级特性。

kotlin
sealed class NetworkState {
    object Loading : NetworkState()
    data class Success(val data: String) : NetworkState()
    data class Error(val message: String) : NetworkState()
}
fun render(state: NetworkState): String = when (state) {
    NetworkState.Loading -> "Spinner"
    is NetworkState.Success -> "Data: ${state.data}"
    is NetworkState.Error -> "Error: ${state.message}"
    // No else needed—compiler enforces all cases
}
// Adding a new subclass causes compile error
// until you handle it

ADT 建模(Result/Either)

Sealed class 建模和类型(Either/Result)用于面向轨道编程。fold 处理两个分支。不同于异常,错误在类型签名中是显式的。Arrow-kt 提供更丰富的 Either/Validated。用于可预测、可组合的错误处理而无需 try/catch。

kotlin
sealed class Either<out L, out R> {
    data class Left<out L>(val value: L) : Either<L, Nothing>()
    data class Right<out R>(val value: R) : Either<Nothing, R>()
}
fun <L, R> Either<L, R>.fold(
    ifLeft: (L) -> Unit,
    ifRight: (R) -> Unit
) = when (this) {
    is Either.Left -> ifLeft(value)
    is Either.Right -> ifRight(value)
}
fun divide(a: Int, b: Int): Either<String, Int> =
    if (b == 0) Either.Left("Divide by zero")
    else Either.Right(a / b)

递归的 Sealed Class

Sealed class 可以是递归的,建模树结构(AST、JSON、表达式)。用 when + 递归进行模式匹配优雅地求值。这是经典的函数式 ADT 用法——类型安全、穷尽且重构友好。用于编译器、解析器和配置 DSL。

kotlin
sealed class Expr {
    data class Num(val value: Int) : Expr()
    data class Add(val left: Expr, val right: Expr) : Expr()
    data class Mul(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int = when (e) {
    is Expr.Num -> e.value
    is Expr.Add -> eval(e.left) + eval(e.right)
    is Expr.Mul -> eval(e.left) * eval(e.right)
}
val expr = Expr.Add(Expr.Num(2), Expr.Mul(Expr.Num(3), Expr.Num(4)))
fun main() = println(eval(expr))  // 14
14

委托属性

lazy 委托

lazy 将初始化推迟到首次访问并缓存结果。默认是线程安全的(双重检查锁定)。用于可能不需要的昂贵资源(配置、数据库连接、单例)。在单线程上下文中传递 NONE 模式以避免同步开销。

kotlin
val heavyConfig: Config by lazy {
    println("Initializing...")
    loadConfigFromFile()  // runs once, first access
}
fun main() {
    println("Before access")
    println(heavyConfig)  // initializes here
    println(heavyConfig)  // cached, no re-init
}
// lazy is thread-safe by default (LazyThreadSafetyMode.SYNCHRONIZED)
// Use LazyThreadSafetyMode.NONE for single-threaded

observable 与 vetoable

observable 在每次更改后触发回调(日志、副作用)。vetoable 可以通过返回 false 拒绝更改(验证)。两者都接受初始值和 lambda。用于响应式状态、验证或触发 UI 更新。对于更复杂的场景,使用自定义委托。

kotlin
import kotlin.properties.Delegates
var count: Int by Delegates.observable(0) { _, old, new ->
    println("Changed: $old -> $new")
}
var age: Int by Delegates.vetoable(0) { _, old, new ->
    new >= 0  // reject negative values
}
fun main() {
    count = 1   // prints "Changed: 0 -> 1"
    count = 2   // prints "Changed: 1 -> 2"
    age = 25    // accepted
    age = -5    // rejected, stays 25
}

自定义属性委托

自定义委托通过 ReadOnlyProperty/ReadWriteProperty 实现 getValue(var 还需 setValue)。它们封装可复用的属性行为(验证、格式化、缓存、数据库映射)。thisRef 是所有者,prop 是属性元数据。对 ORM/序列化框架非常强大。

kotlin
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class TrimmedString : ReadWriteProperty<Any?, String> {
    private var value: String = ""
    override fun getValue(thisRef: Any?, prop: KProperty<*>) = value
    override fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
        this.value = value.trim()
    }
}
class User {
    var name: String by TrimmedString()
}
fun main() {
    val u = User()
    u.name = "  Alice  "
    println(u.name)  // "Alice"
}

Map 支持的属性

委托给 Map 让你将属性绑定到动态键——用于解析 JSON、配置或 ORM 行而无需样板代码。属性名必须匹配 map 键。对可写属性使用 MutableMap。这就是 kotlinx.serialization 和许多 ORM 底层的工作方式。

kotlin
class User(map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
    val email: String? by map
}
fun main() {
    val u = User(mapOf(
        "name" to "Alice",
        "age" to 30,
        "email" to "[email protected]"
    ))
    println(u.name)  // Alice
}
// Mutable version: MutableMap
class MutableUser(map: MutableMap<String, Any?>) {
    var name: String by map
}

notNull 与单例委托

notNull() 类似 lateinit 但适用于任何类型(包括原始类型)且可用于 val/var。在设置前访问会抛出异常。对于单例,优先使用 object(急切)或 by lazy(懒加载)。lateinit 仅用于类中的 var;notNull 委托更灵活但有轻微开销。

kotlin
import kotlin.properties.Delegates
class Service {
    // Late-init for non-null, set once
    var config: String by Delegates.notNull()
    // Throws before initialization
}
// Singleton via object (no delegate needed)
object Database {
    val connection = connect()
}
// Or lazy singleton
val db: Database by lazy { Database() }
fun main() {
    val s = Service()
    // println(s.config)  // IllegalStateException
    s.config = "prod"
    println(s.config)  // prod
}
15

DSL 构建

类型安全构建器

类型安全构建器使用带接收者的函数类型(带接收者的 lambda)创建嵌套 DSL。lambda 在接收者(this)的上下文中运行,因此你可以直接调用其方法。这就是 Kotlin 的 HTML DSL、Gradle 构建脚本和 kotlinx.html 的工作方式——声明式且编译时检查。

kotlin
class Table {
    private val rows = mutableListOf<Row>()
    fun row(init: Row.() -> Unit) {
        val r = Row(); r.init(); rows.add(r)
    }
    fun build() = rows
}
class Row {
    private val cells = mutableListOf<String>()
    fun cell(text: String) { cells.add(text) }
    fun build() = cells
}
fun table(init: Table.() -> Unit) = Table().apply(init).build()
val t = table {
    row { cell("A"); cell("B") }
    row { cell("C"); cell("D") }
}

@DslMarker(作用域控制)

@DslMarker 防止隐式接收者泄漏——在嵌套 DSL 块内,你只能调用最内层接收者的方法。没有它,Kotlin 会允许调用外部接收者的方法,导致令人困惑/有 bug 的 DSL。用相同的标记注解注解所有 DSL 类。

kotlin
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HTML { fun body(b: Body.() -> Unit) { /*...*/ } }
@HtmlDsl
class Body { fun p(text: String) { /*...*/ } }
fun html(init: HTML.() -> Unit) = HTML().apply(init)
// Without @DslMarker, inner 'this' could call outer methods
html {
    body {
        // p() is unambiguous—Body's method
        // body() would be an error (not Body's method)
        p("Hello")
    }
}

中缀函数

infix 函数启用自然语言语法(a to b、1 until 10)。它们必须是带单个参数的成员或扩展函数。在 DSL、测试框架和数学库中大量使用。内置示例:to(Pair)、until/step(范围)和集合操作。

kotlin
infix fun Int.times(str: String) = str.repeat(this)
fun main() {
    println(3 times "ab")  // "ababab"
    // Equivalent to: 3.times("ab")
    // No dot, no parentheses—reads like natural language
}
// Common in testing
infix fun <T> T.shouldEqual(expected: T) =
    assert(this == expected)
fun test() { 5 shouldEqual 5 }
// Also: to, until, step, in, etc.

运算符重载

运算符重载让对象使用 +、-、*、[]、() 等自然语法。用 operator 标记函数。合理重载——数学类型(Vec、Matrix、Money)受益;任意重载损害可读性。get/set 启用索引;invoke 使对象可调用。范围运算符(rangeTo、contains)驱动 for 循环。

kotlin
data class Vec(val x: Int, val y: Int) {
    operator fun plus(o: Vec) = Vec(x + o.x, y + o.y)
    operator fun minus(o: Vec) = Vec(x - o.x, y - o.y)
    operator fun times(s: Int) = Vec(x * s, y * s)
    operator fun unaryMinus() = Vec(-x, -y)
    operator fun get(i: Int) = when (i) { 0 -> x; 1 -> y; else -> throw IndexOutOfBoundsException() }
}
fun main() {
    val a = Vec(1, 2)
    val b = Vec(3, 4)
    println(a + b)        // Vec(x=4, y=6)
    println(a * 2)        // Vec(x=2, y=4)
    println(-a)           // Vec(x=-1, y=-2)
    println(a[0])         // 1
}

带接收者的函数类型

带接收者的函数类型(A.(B) -> C)让 lambda 以 'this' 访问接收者。这是 Kotlin DSL 和作用域函数(apply、run、with)的基础。apply 返回接收者;run 返回 lambda 结果。掌握它以构建富有表现力、类型安全的内部 DSL。

kotlin
// Lambda with receiver: A.(B) -> C
val greet: String.(Int) -> String = { times -> repeat(times) { this + "!" } }
fun main() {
    println("Hello".greet(3))  // Hello!!!Hello!!!Hello!!!
    // 'this' is the String receiver
}
// Build DSLs with receiver
class StringBuilder {
    private val parts = mutableListOf<String>()
    fun add(s: String) { parts.add(s) }
}
fun build(init: StringBuilder.() -> Unit) =
    StringBuilder().apply(init)
val sb = build { add("a"); add("b") }
16

测试(JUnit 5、MockK、Turbine)

JUnit 5 基础

JUnit 5(Jupiter)是标准的 Kotlin 测试框架。@Test 标记测试;@BeforeEach/@AfterEach 运行每测试设置/拆卸。使用反引号命名以提升可读性。@ParameterizedTest + @ValueSource 用多个输入运行测试。assertThrows 检查异常。@Disabled 跳过测试。

kotlin
import org.junit.jupiter.api.*
import org.junit.jupiter.api.Assertions.*
class CalculatorTest {
    lateinit var calc: Calculator
    @BeforeEach fun setup() { calc = Calculator() }
    @Test fun `add two numbers`() {
        assertEquals(5, calc.add(2, 3))
    }
    @Test fun `divide by zero throws`() {
        assertThrows<ArithmeticException> { calc.divide(10, 0) }
    }
    @ParameterizedTest
    @ValueSource(ints = [1, 2, 3])
    fun `positive numbers`(n: Int) { assertTrue(n > 0) }
    @Disabled("TODO") @Test fun skip() {}
}

MockK(模拟)

MockK 是 Kotlin 地道的模拟库(处理 final 类、协程、扩展函数)。every { } returns/throws 桩;verify { } 检查调用。使用 relaxed = true 跳过 void 方法的桩。MockK 通过 coEvery/coVerify 原生支持 suspend 函数——对协程测试至关重要。

kotlin
import io.mockk.*
interface UserService { fun find(id: Int): String?; fun save(name: String): Int }
class Test {
    val mock = mockk<UserService>()
    @Test fun test() {
        every { mock.find(1) } returns "Alice"
        every { mock.find(any()) } returns null
        every { mock.save(any()) } throws RuntimeException("fail")
        verify { mock.find(1) }              // called once
        verify(exactly = 2) { mock.find(any()) }
        confirmVerified(mock)
    }
}
// relaxUnitFun / relaxed = true for void methods

Turbine(Flow 测试)

Turbine 是测试 Flow 的标准库。test { } 在受控作用域中收集;awaitItem() 断言下一个发射,awaitComplete()/awaitError() 检查终止。它正确处理时序和虚拟时间。与 runTest 结合进行协程友好、快速、确定性的 Flow 测试。

kotlin
import app.cash.turbine.test
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.test.runTest
import kotlin.test.*
class FlowTest {
    @Test fun testFlow() = runTest {
        flowOf(1, 2, 3).test {
            assertEquals(1, awaitItem())
            assertEquals(2, awaitItem())
            assertEquals(3, awaitItem())
            awaitComplete()
        }
    }
    @Test fun testError() = runTest {
        flow<Int> { throw RuntimeException("x") }.test {
            assertTrue(awaitError() is RuntimeException)
        }
    }
}

协程测试(runTest)

runTest 使用虚拟时间——延迟瞬间跳过,使异步测试快速且确定。使用 StandardTestDispatcher + advanceUntilIdle() 进行手动控制。将 TestDispatcher 注入生产代码(替代 Dispatchers.Main)以获得可测试性。避免在测试中使用 runBlocking——runTest 是现代方式。

kotlin
import kotlinx.coroutines.test.*
import kotlinx.coroutines.*
import kotlin.test.*
class RepoTest {
    @Test fun fetch() = runTest {
        // Virtual time: delay(1000) completes instantly
        val result = async { delay(1000); "data" }.await()
        assertEquals("data", result)
    }
    @Test fun withDispatcher() = runTest {
        val scheduler = StandardTestDispatcher()
        val scope = CoroutineScope(scheduler)
        // advanceUntilIdle() runs pending coroutines
        scope.launch { /*...*/ }
        scheduler.advanceUntilIdle()
    }
}

Kotest 断言与风格

Kotest 提供富有表现力的断言(shouldBe、shouldContain)和多种规范风格(StringSpec、FunSpec、BehaviorSpec 用于 BDD)。它与基于属性的测试集成并拥有丰富的匹配器。在 Kotlin 地道、可读的测试上选择它而非 JUnit。它可以与 JUnit 5 并行运行。

kotlin
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.collections.shouldContain
import io.kotest.matchers.string.shouldStartWith
class MyTest : StringSpec({
    "addition works" {
        1 + 1 shouldBe 2
    }
    "list contains" {
        listOf(1, 2, 3) shouldContain 2
    }
    "string prefix" {
        "hello" shouldStartWith "he"
    }
})
// Other styles: FunSpec, BehaviorSpec, ShouldSpec
17

函数式编程深入

作用域函数(let、run、with、apply、also)

作用域函数在接收者(this vs it)和返回值(this vs 结果)上不同。apply/also 返回接收者(链式、构建器);let/run/with 返回 lambda 结果(转换)。使用 apply 进行配置,let 进行空检查/转换,also 进行副作用。不要过度使用——可读性优先。

kotlin
data class User(var name: String, var age: Int)
fun main() {
    val u = User("A", 1)
    // apply: configure & return this (builders)
    val configured = u.apply { age = 30 }
    // let: transform, it = receiver, returns lambda result
    val name = u.let { it.name.uppercase() }
    // run: transform, this = receiver, returns result
    val s = u.run { "$name:$age" }
    // with: like run but called as function
    val str = with(u) { "$name:$age" }
    // also: side effects, returns this (logging/chaining)
    u.also { println("Created $it") }
}

Sequence(惰性求值)

Sequence 惰性求值——每个元素在下一个开始前流经整个管道(类似 Java Stream)。这避免了中间集合并支持短路(take/find)。用于大数据集或多步管道。对于小列表,急切的 List 通常更快(开销更少)。generateSequence 构建无限流。

kotlin
fun main() {
    // Eager: List processes each step fully
    val eager = (1..10).toList()
        .map { it * 2 }
        .filter { it > 5 }
    // Lazy: Sequence processes element-by-element
    val lazy = (1..10).asSequence()
        .map { println("map $it"); it * 2 }
        .filter { println("filter $it"); it > 5 }
        .take(2)
        .toList()  // only processes until 2 found
    // Use Sequence for large/pipelined data
    // generateSequence for infinite streams
    val naturals = generateSequence(1) { it + 1 }
}

内联函数与 reified

inline 通过内联字节码消除 lambda 对象/分配开销——对热循环至关重要。它还启用非局部返回(在 lambda 内从外部函数返回)。reified 类型参数使泛型类型信息在运行时可用(T::class、is T),但需要 inline。用于类型安全的辅助函数如 filterIsInstance。

kotlin
// inline: copies bytecode at call site (no lambda overhead)
inline fun measure(block: () -> Unit): Long {
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}
// non-local return: inline lambdas can return from outer fun
inline fun forEach(list: List<Int>, f: (Int) -> Unit) {
    for (i in list) f(i)
}
// reified: access generic type at runtime
inline fun <reified T> List<*>.filterIsInstance() =
    filter { it is T } as List<T>
val strs = listOf(1, "a", 2, "b").filterIsInstance<String>()

高阶函数

高阶函数接收或返回函数。它们是函数式 Kotlin 的骨干——map、filter、fold、compose。(Int) -> Int 等函数类型是一等的。compose 构建管道。使用它们抽象模式、实现代码复用并编写声明式代码。对性能关键路径标记 inline。

kotlin
fun <T, R> List<T>.mapTo(transform: (T) -> R): List<R> {
    val result = mutableListOf<R>()
    for (item in this) result.add(transform(item))
    return result
}
fun compose(f: (Int) -> Int, g: (Int) -> Int): (Int) -> Int =
    { x -> f(g(x)) }
fun main() {
    val double = { x: Int -> x * 2 }
    val inc = { x: Int -> x + 1 }
    val doubleThenInc = compose(inc, double)
    println(doubleThenInc(3))  // 7
    listOf(1, 2, 3).mapTo { it * it }
}

递归与 tailrec

tailrec 将尾递归函数(递归是最后一个操作)转换为循环,防止栈溢出。递归调用必须在尾位置——无待处理的乘法/加法。使用累加器参数使函数尾递归。对大输入上的函数式风格循环至关重要。

kotlin
// Regular recursion: stack overflow on large n
fun factorial(n: Int): Long =
    if (n <= 1) 1 else n * factorial(n - 1)
// tailrec: compiler optimizes to a loop (no stack growth)
tailrec fun factorialTail(n: Int, acc: Long = 1): Long =
    if (n <= 1) acc else factorialTail(n - 1, acc * n)
// Fibonacci with tailrec
tailrec fun fib(n: Int, a: Long = 0, b: Long = 1): Long =
    when (n) { 0 -> a; 1 -> b; else -> fib(n - 1, b, a + b) }
fun main() {
    println(factorialTail(10000))  // no stack overflow
}
18

泛型与型变

泛型类与函数

泛型实现类型安全、可复用的代码。类使用 <T>;函数在返回类型前声明 <T>。约束(T : Entity)限制类型边界。不同于 Java,Kotlin 的泛型对内联函数是具体化的,并具有声明处型变,使泛型 API 更安全、更易用。

kotlin
class Box<T>(val value: T) {
    fun get(): T = value
}
fun <T> singletonList(item: T): List<T> = listOf(item)
fun main() {
    val intBox = Box(42)        // Box<Int> inferred
    val strBox = Box<String>("hi")
    val nums = singletonList(1)
}
// Generic constraints
class Repository<T : Entity> {
    fun save(item: T) { /* T is Entity subtype */ }
}
interface Entity { val id: Int }

型变(in/out)

型变控制泛型的子类型关系。out(协变):Producer<Dog> 是 Producer<Animal>——安全因为你只读取 T。in(逆变):Sink<Animal> 是 Sink<Dog>——安全因为你只写入 T。可变集合是不变的(读 + 写)。使用 out/in 设计安全、灵活的 API。

kotlin
// out (covariant): Producer<Sub> is Producer<Super>
interface Source<out T> { fun next(): T }
// in (contravariant): Consumer<Super> is Consumer<Sub>
interface Sink<in T> { fun put(item: T) }
// invariant (default): neither
class MutableList<T> {
    fun add(item: T) {}
    fun get(): T = TODO()
}
open class Animal
class Dog : Animal()
val src: Source<Animal> = Source<Dog>()  // OK (out)
val sink: Sink<Dog> = Sink<Animal>()     // OK (in)

型变实践

函数类型自动使用型变:参数是 'in',返回类型是 'out'。这就是为什么 (Dog) -> Unit 可赋值给 (Animal) -> Unit。PECS 原则(Producer Extends、Consumer Super)映射到 Kotlin 的 out/in。设计泛型接口时,只生产 T 用 out,只消费 T 用 in。

kotlin
// Function types are variant by nature
// (T) -> R is contravariant in T, covariant in R
val dogHandler: (Dog) -> Unit = { println(it) }
val animalHandler: (Animal) -> Unit = dogHandler  // OK
// Covariant return types
interface Repository<out T> { fun find(id: Int): T }
class DogRepo : Repository<Dog> {
    override fun find(id: Int): Dog = Dog()
}
val repo: Repository<Animal> = DogRepo()  // OK
// PECS rule: Producer extends, Consumer super
// Kotlin: out = producer, in = consumer

类型投影与星投影

类型投影在使用点临时使类型变体。Array<out Any> 表示'某个 Any 子类型的数组,可读'。Array<in Any> 表示'可作为 Any 写入'。星投影(*)表示'某个未知类型'——当你只需要 size/contains 而非元素类型时有用。类似 Java 的通配符。

kotlin
fun copy(from: Array<out Any>, to: Array<in Any>) {
    // from: only read (covariant projection)
    // to: only write (contravariant projection)
    for (i in from.indices) to[i] = from[i]
}
// Star projection: unknown type, read-only
fun printSize(list: List<*>) {
    println(list.size)  // OK (no type needed)
    // list[0]  // type is Any? — limited use
}
val mixed: List<*> = listOf(1, "a", 3.0)
// Use when type is irrelevant or unknown

具体化类型参数

reified 在运行时保留泛型类型信息(通常在 JVM 上被擦除)。它需要 inline(类型在每个调用点已知)。启用 is T 检查、T::class 和 filterIsInstance。没有 reified,你需要手动传递 Class<T> 参数。限制:reified 类型不能用于非内联函数或作为类类型参数。

kotlin
// reified requires inline
inline fun <reified T> List<*>.filterIsInstance(): List<T> =
    filter { it is T } as List<T>
inline fun <reified T> Any.castTo(): T = this as T
inline fun <reified T> typeOf() = T::class.simpleName
fun main() {
    val list = listOf(1, "a", 2, "b", 3)
    val strs: List<String> = list.filterIsInstance()
    println(strs)  // [a, b]
    println(typeOf<String>())  // String
    // Without reified, T is erased at runtime
    // reified makes T::class and 'is T' available
}
19

空安全深入

可空类型

Kotlin 在编译时区分可空(T?)和非空(T)类型。?. 安全调用在接收者为 null 时返回 null。?: Elvis 提供默认值。!! 抛出 NPE(避免使用)。编译器强制执行 null 检查,消除了 Kotlin 代码中的 NullPointerException。

kotlin
var name: String = "Alice"  // Non-null
// name = null  // Error
var nickname: String? = null  // Nullable
// Safe call
println(nickname?.length)  // null if nickname is null
// Elvis operator
val len = nickname?.length ?: 0  // 0 if null
// Not-null assertion (use sparingly)
val len2 = nickname!!.length  // NPE if null

let 与 run

let 在值非空时执行块(it = 该值)。run 以对象为接收者调用块,返回结果。apply 配置对象,返回对象。also 执行副作用,返回对象。这些作用域函数减少了 null 检查并提升了可读性。

kotlin
val name: String? = "Alice"
name?.let {
    println("Length: ${it.length}")  // Only if not null
}
// run: object + block
val result = "Hello".run {
    length  // Returns 5
}
// apply: configure object
val list = mutableListOf<Int>().apply {
    add(1); add(2); add(3)
}

lateinit

lateinit 推迟非空属性的初始化。不能用于原始类型或可空类型。在初始化前访问会抛出 UninitializedPropertyAccessException。::prop.isInitialized 检查。用于依赖注入和生命周期管理的属性。

kotlin
class Service {
    lateinit var dependency: Database
    fun init() {
        dependency = connectDatabase()
    }
    fun use() {
        if (::dependency.isInitialized) {
            dependency.query()
        }
    }
}

可空集合

集合可以持有可空元素(List<String?>)或本身可空(List<String>?)。filterNotNull 移除 null。firstOrNull 返回 null 而非抛出异常。用安全调用处理可空元素。在集合类型中明确空性。

kotlin
val list: List<String?> = listOf("a", null, "b")
val filtered = list.filterNotNull()  // ["a", "b"]
val lengths = list.map { it?.length }  // [1, null, 1]
val first = list.firstOrNull()  // "a" or null
val firstNonEmpty = list.firstOrNull { it?.isNotEmpty() == true }

平台类型

平台类型来自 Java 互操作,其中空性未知。Kotlin 无法为它们强制空安全。始终为 Java 返回值显式声明可空类型。在 Java 中使用 @Nullable/@NotNull 注解。JSR-305 注解帮助 Kotlin 推断空性。

kotlin
// Java interop: platform type (no null info)
val name: String = javaObject.getName()  // May be null!
// Kotlin does not know if Java returns null
// Fix: explicit nullable type
val name2: String? = javaObject.getName()
// Or @Nullable annotation in Java
20

DSL 构建

带 Lambda 接收者的构建器

带接收者的 lambda(T.() -> Unit)启用 DSL 语法。在 lambda 内,this 是接收者对象。方法可以无限定调用。这就是 Kotlin 构建类型安全 DSL 如 Gradle、HTML 和 SQL 构建器的方式。

kotlin
class StringBuilder {
    private val parts = mutableListOf<String>()
    fun line(s: String) { parts.add(s) }
    fun build() = parts.joinToString("\n")
}
fun buildString(init: StringBuilder.() -> Unit): String {
    val sb = StringBuilder()
    sb.init()  // Execute lambda with sb as receiver
    return sb.build()
}

HTML DSL

HTML DSL 使用带 lambda 接收者的嵌套构建器。每个标签是一个创建子构建器的函数。lambda 配置子级。产生类型安全、可组合的 HTML。kotlinx.html 是一个真实实现。相同模式适用于任何层次结构。

kotlin
fun html(init: HTML.() -> Unit): HTML {
    val h = HTML(); h.init(); return h
}
class HTML {
    fun body(init: Body.() -> Unit) { /* ... */ }
}
html {
    body {
        // this: Body
        p("Hello")
    }
}

@DslMarker

@DslMarker 防止对外部作用域的隐式接收者访问。没有它,HTML 和 Body 方法都可访问,导致混乱。该注解限制对最内层接收者的访问。使 DSL 类型安全且无歧义。对复杂 DSL 至关重要。

kotlin
@DslMarker
annotation class HtmlDsl
@HtmlDsl
class HTML { fun body(...) {} }
@HtmlDsl
class Body { fun p(...) {} }
html {
    body {
        // p()  // OK: in Body scope
        // body()  // Error: in Body scope, not HTML
    }
}

Gradle DSL

Gradle Kotlin DSL 使用相同的构建器模式。plugins、dependencies 是带接收者 lambda 的函数。implementation、testImplementation 是依赖配置函数。类型安全:编译器检查函数名和参数类型。比重构 Groovy 好得多。

kotlin
plugins {
    kotlin("jvm") version "1.9.0"
}
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    testImplementation(kotlin("test"))
}
// All are function calls with lambda receivers

Anko SQL

Anko(和 Exposed)提供类型安全的 SQL DSL。列比较是类型化的。不能将字符串列与 int 比较。DSL 生成 SQL。防止 SQL 注入和类型错误。相同模式适用于任何领域特定语言。

kotlin
fun users(where: SqlExpressionBuilder.() -> Op<Boolean>) {
    // DSL for SQL queries
}
users {
    (Users.age greater 18) and (Users.name like "A%")
}
// Type-safe SQL: compiler checks column types
21

集合

List 操作

Kotlin 集合拥有丰富的函数式 API。map、filter、reduce 是标准的。groupBy 按键分区。chunked 分成固定大小的列表。windowed 创建滑动窗口。都返回新集合。使用 asSequence() 进行惰性求值。

kotlin
val list = listOf(1, 2, 3, 4, 5)
val doubled = list.map { it * 2 }
val evens = list.filter { it % 2 == 0 }
val sum = list.reduce { acc, n -> acc + n }
val grouped = list.groupBy { it % 2 }  // {0=[2,4], 1=[1,3,5]}
val chunked = list.chunked(2)  // [[1,2],[3,4],[5]]

Sequence

Sequence 是惰性集合(类似 Java Stream)。操作按需求值。无中间集合。对带链式操作的大型集合高效。使用 asSequence() 转换。用 toList()、toSet() 等强制求值。

kotlin
val result = (1..1000000).asSequence()
    .map { it * 2 }
    .filter { it > 100 }
    .take(10)
    .toList()
// Lazy: only computes what is needed
// No intermediate collections

解构

解构将对象拆分为变量。适用于 Pair、Triple、data class 和 Map 条目。componentN() 函数启用它。withIndex() 配对索引和值。用于多个返回值和迭代。Data class 自动生成 componentN。

kotlin
val (a, b) = Pair(1, "hello")
val (x, y, z) = Triple(1, 2, 3)
for ((index, value) in list.withIndex()) {
    println("$index: $value")
}
data class Point(val x: Int, val y: Int)
val (px, py) = Point(1, 2)

可变与不可变

Kotlin 同时有可变(MutableList)和不可变(List)集合。优先使用不可变以获得安全性。toList() 创建不可变副本。toMutableList() 创建可变副本。不可变接口不暴露修改方法,防止意外修改。

kotlin
val mutable = mutableListOf(1, 2, 3)
mutable.add(4)  // OK
val immutable = listOf(1, 2, 3)
// immutable.add(4)  // Error: no add method
// Convert
val imm = mutable.toList()
val mut = imm.toMutableList()

Associate 与 Partition

toMap/associate 转换为映射。partition 按谓词拆分为两个列表。flatten 合并嵌套列表。flatMap 映射并扁平化。这些用声明式表达式替代冗长的循环。都返回新集合。

kotlin
val list = listOf("a" to 1, "b" to 2)
val map = list.toMap()  // {a=1, b=2}
val byLength = list.associate { it.first to it.second }
val (evens, odds) = list.partition { it.second % 2 == 0 }
// evens: [(a,1)?] odds: [(b,2)]
val flat = listOf(listOf(1), listOf(2, 3)).flatten()  // [1,2,3]
22

常见陷阱

== 与 ===

Kotlin 的 == 调用 equals(值相等),不同于 Java。=== 检查引用相等性。使用 == 进行值比较。=== 很少需要。对于 Integer,值 -128 到 127 被缓存,因此 === 可能为真或假。始终使用 == 进行值比较。

kotlin
val a = Integer(127)
val b = Integer(127)
a == b  // true (value equality, calls equals)
a === b  // true (Integer caches -128 to 127)
val c = Integer(128)
val d = Integer(128)
c == d  // true
c === d  // false (not cached)

Companion Object

Kotlin 没有静态成员。companion object 持有'静态'方法和常量。const val 是真正的编译时常量。companion object 是单例实例。@JvmStatic 使方法可从 Java 作为静态调用。使用顶层函数实现真正的静态。

kotlin
class MyClass {
    companion object {
        const val CONSTANT = 42
        fun create() = MyClass()
    }
}
MyClass.CONSTANT  // 42
MyClass.create()  // Static-like access
// companion object is a real object, not static

Data Class Copy

Data class 自动生成 copy(),创建修改后的副本。原始不变(不可变)。仅指定字段更改。用于更新。与解构结合,data class 对建模数据非常强大。在 data class 中避免 var 以获得不可变性。

kotlin
data class User(val name: String, val age: Int)
val alice = User("Alice", 30)
val older = alice.copy(age = 31)  // New instance
// alice is unchanged
// copy uses named arguments for changed fields

Sealed Class 穷尽性

Sealed class 将子类型限制在同一文件/包中。对 sealed class 的 when 是穷尽的:如果缺少分支,编译器会警告。如果覆盖所有 case,则无需 else。添加新子类型会在各处产生警告。非常适合状态机和结果。

kotlin
sealed class Result
data class Success(val value: Int) : Result()
data class Failure(val error: String) : Result()
fun handle(r: Result) = when (r) {
    is Success -> r.value
    // Warning: Missing Failure branch
}
// Fix: handle all cases
fun handle(r: Result) = when (r) {
    is Success -> r.value
    is Failure -> 0
}  // No else needed

扩展与成员

成员函数始终优先于具有相同签名的扩展函数。扩展在编译时解析(静态分派),成员在运行时解析(动态分派)。扩展不能覆盖成员。将扩展用于工具函数,而非多态。

kotlin
class Foo {
    fun bar() = "member"  // Member function
}
fun Foo.bar() = "extension"  // Extension function
Foo().bar()  // "member" - members win!
// Extensions are resolved statically
// Members are resolved dynamically (virtual)
23

Kotlin 多平台

公共代码

Kotlin 多平台(KMP)跨平台共享代码。expect/actual 声明平台特定实现。commonMain 包含共享代码。平台特定源集实现 actual。共享业务逻辑,保持 UI 原生。Gradle 配置目标。

kotlin
// commonMain/src/Platform.kt
expect fun getPlatformName(): String
// androidMain/src/Platform.kt
actual fun getPlatformName(): String = "Android"
// iosMain/src/Platform.kt
actual fun getPlatformName(): String = "iOS"
// Shared business logic
class Greeting {
    fun greet() = "Hello from ${getPlatformName()}"
}

共享模块

多平台项目在 Gradle 中使用 kotlin 块。定义目标(android、ios)。commonMain 有共享依赖。平台源集可以有平台特定依赖。iOS 使用 Kotlin/Native 直接编译。共享逻辑,不共享 UI。

kotlin
// build.gradle.kts (shared module)
kotlin {
    androidTarget()
    iosX64(); iosArm64(); iosSimulatorArm64()
    sourceSets {
        val commonMain by getting {
            dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") }
        }
    }
}

网络(Ktor)

Ktor 是多平台 HTTP 客户端。API 是公共的,引擎是平台特定的。Android 使用 OkHttp/Android 引擎。iOS 使用 Darwin。协程跨平台工作。共享网络、数据库(SQLDelight)和业务逻辑。

kotlin
// Shared HTTP client
suspend fun fetchUser(): User {
    return client.get("https://api.example.com/user").body()
}
// Platform-specific engine
// Android: OkHttp or Android
// iOS: Darwin
// Common code uses the same API

SQLDelight

SQLDelight 从 SQL 生成类型安全的 Kotlin。.sq 文件包含带命名查询的 SQL。生成类型安全的查询对象。跨平台工作(Android、iOS、JVM)。SQL 是真理之源。跟踪模式迁移。是多平台 Room 的替代方案。

kotlin
// .sq file: User.sq
CREATE TABLE User (id INTEGER, name TEXT);
selectById: SELECT * FROM User WHERE id = ?;
insertUser: INSERT INTO User (id, name) VALUES (?, ?);
// Generated Kotlin code
val queries: UserQueries = database.userQueries
queries.insertUser(1, "Alice")
val user = queries.selectById(1).executeAsOne()

Compose 多平台

Compose 多平台将 Jetpack Compose 扩展到 iOS、桌面和 Web。跨平台共享 UI 代码。相同的 @Composable API。平台特定入口点。对 iOS 仍是实验性的。减少 UI 重复。Gradle 插件:compose-multiplatform。

kotlin
// Shared UI with Compose Multiplatform
@Composable
fun Greeting(name: String) {
    Text("Hello, $name!")
}
// Android
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { Greeting("World") }
    }
}
// Desktop
fun main() = application {
    Window { Greeting("Desktop") }
}
24

测试 Kotlin

JUnit 5

JUnit 5 是标准测试框架。Kotlin 允许反引号测试名以提升可读性。assertEquals、assertThrows 是常见断言。@BeforeEach、@AfterEach 用于设置/拆卸。@ParameterizedTest 用于数据驱动测试。多平台使用 kotlin.test。

kotlin
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*
class CalculatorTest {
    @Test
    fun `test addition`() {
        val calc = Calculator()
        assertEquals(5, calc.add(2, 3))
    }
    @Test
    fun `throws on negative`() {
        assertThrows<IllegalArgumentException> { calc.sqrt(-1) }
    }
}

MockK

MockK 是 Kotlin 原生模拟库。支持 final 类和扩展函数。every 桩,verify 检查调用。@MockK 创建模拟,@InjectMockKs 注入它们。coEvery/coVerify 用于协程。比 Mockito 有更好的 Kotlin 支持。

kotlin
class UserServiceTest {
    @MockK
    lateinit var repo: UserRepository
    @InjectMockKs
    lateinit var service: UserService
    @BeforeEach
    fun setup() {
        MockKAnnotations.init(this)
        every { repo.find(1) } returns User("Alice")
    }
    @Test
    fun `test find`() {
        assertEquals("Alice", service.find(1).name)
        verify { repo.find(1) }
    }
}

Kotest

Kotest 是 Kotlin 优先的测试框架。多种风格:StringSpec、BehaviorSpec、FunSpec。shouldBe 是流式断言。用 Arb 进行属性测试。支持数据驱动测试。与 Spring 和 Ktor 集成。比 JUnit 更 Kotlin 地道。

kotlin
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
class CalculatorTest : StringSpec({
    "addition should work" {
        Calculator().add(2, 3) shouldBe 5
    }
    "should throw on negative" {
        shouldThrow<IllegalArgumentException> { Calculator().sqrt(-1) }
    }
})

协程测试

kotlinx-coroutines-test 的 runTest 提供虚拟时间。延迟瞬间跳过。advanceUntilIdle 运行待处理的协程。比真实时间快得多。使用 TestDispatcher 进行精细控制。Turbine 库测试 Flow 发射。

kotlin
@Test
fun `test async`() = runTest {
    val result = fetchData()
    assertEquals("data", result)
}
// runTest replaces runBlocking with virtual time
// advanceUntilIdle() runs all pending coroutines
// Delay skips instantly

Turbine(Flow 测试)

Turbine 测试 Flow 发射。awaitItem 获取下一个发射。awaitComplete 断言完成。awaitError 断言错误。test 块挂起直到流完成。比手动收集干净得多。对 Flow 测试至关重要。

kotlin
@Test
fun `test flow`() = runTest {
    flowOf(1, 2, 3).test {
        awaitItem() shouldBe 1
        awaitItem() shouldBe 2
        awaitItem() shouldBe 3
        awaitComplete()
    }
}
// Test errors
flow<Int> { throw Exception() }.test {
    awaitError() shouldBe Exception()
}
25

协程深入

协程作用域

CoroutineScope 为协程定义生命周期。viewModelScope 在 VM 清除时自动取消。SupervisorJob 防止子级失败取消兄弟。自定义作用域需要显式取消。作用域将取消传播给子级。生产中永远不要使用 GlobalScope(无法取消)。

kotlin
class MyViewModel : ViewModel() {
    fun fetchData() {
        viewModelScope.launch {
            val data = api.getData()
            _data.value = data
        }
    }
}
// Custom scope
val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
scope.launch { /* ... */ }
scope.cancel()  // Cancel all children

调度器

Dispatchers 将协程路由到线程池。Main:UI 线程(Android)。IO:阻塞 I/O(64+ 线程)。Default:CPU 工作(CPU 数线程)。Unconfined:调用者线程(高级)。withContext 临时切换调度器。选择正确的调度器可提升性能并防止 UI 冻结。

kotlin
launch(Dispatchers.Main) { updateUI() }
launch(Dispatchers.IO) { writeFile() }
launch(Dispatchers.Default) { heavyComputation() }
launch(Dispatchers.Unconfined) { runAnywhere() }
// Switch context
withContext(Dispatchers.IO) {
    val data = readDisk()
}

Flow 运算符

Flow 运算符:map 转换,filter 选择,flatMapMerge/flatMapConcat 链式流。冷流每个收集器重新开始。StateFlow 持有值(类似 LiveData)。SharedFlow 向多个收集器广播。stateIn 将冷转换为热。buffer/conflate 控制背压。

kotlin
flowOf(1, 2, 3, 4, 5)
    .map { it * 2 }
    .filter { it > 4 }
    .collect { println(it) }  // 6, 8, 10

// Cold flow: emits per collector
// StateFlow: hot, stateful
// SharedFlow: hot, broadcast
val state = MutableStateFlow(0)
state.value = 1

异常处理

CoroutineExceptionHandler 捕获 launch 中未捕获的异常。async 异常传播到 await。SupervisorJob 隔离子级失败。CancellationException 是特殊的:重新抛出,不被 catch 捕获。永远不要吞掉 CancellationException。使用 try/finally 或 use() 进行清理。取消通过 suspend 调用传播。

kotlin
val handler = CoroutineExceptionHandler { _, e ->
    Log.e("TAG", "Caught: $e", e)
}
scope.launch(handler) {
    throw RuntimeException("oops")
}
// try/catch in coroutine
try {
    riskyCall()
} catch (e: Exception) {
    // handle
}

Channel

Channel 允许协程间通信。满时 send 挂起,空时 receive 挂起。容量:缓冲(RENDEZVOUS=0、UNLIMITED、CONFLATED)。close() 信号完成。produce 创建生产者协程。Channel 是热的:值被消费一次。大多数用例优先使用 Flow。

kotlin
val channel = Channel<Int>(capacity = 10)
launch {
    for (i in 1..5) channel.send(i)
    channel.close()
}
launch {
    for (x in channel) println(x)
}
// Produce pattern
fun numbers() = produce {
    for (i in 1..10) send(i)
}

这篇内容对您有帮助吗?

学习路径

从零开始学习

通过结构化课程从头学习这个语言。