上一篇 下一篇 回到顶部 目录 返回首页
目录

Kotlin 高阶特性从入门到精通:深入语言核心实战全指南

发表于
更新于
4 69.6~89.5 分钟 31317

前言

Kotlin 是一门现代、简洁且表达力极强的 JVM 语言。很多开发者停留在"会用 data class、let/run/apply、基础协程"的阶段,却很少触及 Kotlin 真正强大的高级特性。

本文以 Kotlin 官方文档为基准,深入讲解 Kotlin 的高阶内容:内联函数与 reified、泛型与型变、扩展函数与属性、委托属性、密封类、高阶函数、Flow 异步流、协程高级用法、类型安全 DSL 构建、Operator 重载、中缀调用。掌握这些,你才能写出真正"Kotlin 风格"的代码。


第一部分:内联函数与 reified 类型

1.1 为什么需要 inline

高阶函数会带来运行时开销——Lambda 会被编译为对象分配、闭包捕获、虚方法调用。内联函数在编译期将函数体和传入的 Lambda 直接展开到调用点,消除这些开销。

// 不使用 inline:Lambda 会创建对象、虚方法调用
fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try { return body() } finally { lock.unlock() }
}

// 使用 inline:编译后直接展开,零额外开销
inline fun <T> lock(lock: Lock, body: () -> T): T {
    lock.lock()
    try { return body() } finally { lock.unlock() }
}

// 调用
lock(myLock) { computeSomething() }
// 编译后等效于:
// myLock.lock()
// try { computeSomething() } finally { myLock.unlock() }

使用建议

  • 内联会增大生成代码体积。小函数(如 lockrunCatching、标准库的 let/also/apply)收益最大

  • 大型函数或循环体内的内联收益递减,应谨慎使用

1.2 noinline —— 部分 Lambda 不内联

有时内联函数中的某个 Lambda 需要存储到变量、传入其他非内联函数,这时用 noinline 阻止其内联:

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
    // inlined 被内联展开到调用点
    inlined()
    // notInlined 保持为普通 Lambda 对象,可以传递
    executor.execute(notInlined)
}

1.3 crossinline —— 禁止非局部返回

当 Lambda 在另一个执行上下文中被调用(如嵌套对象、内部函数),非局部返回是不安全的。用 crossinline 标记:

inline fun f(crossinline body: () -> Unit) {
    val f = object : Runnable {
        override fun run() = body()  // body 在其他上下文中执行,不允许 return
    }
    f.run()
}

// 如果不用 crossinline,body 中的 return 会尝试从 f 返回,这是非法的

1.4 reified —— 在函数体内获取泛型运行时类型

Java 的泛型在运行期会被擦除,Kotlin 的 reified 借助内联在编译期将类型信息直接注入到调用点的字节码中,使你在函数体内使用 is / as / ::class

// 没有 reified:需要传入 Class<T>
fun <T> Activity.find(type: Class<T>): T {
    return findViewById(type, R.id.container)
}
find(MyView::class.java)

// 有 reified:直接推断类型
inline fun <reified T> Activity.find(): T {
    return findViewById(T::class.java, R.id.container)
}
find<MyView>()

// 实际应用场景:类型安全的 Intent 启动
inline fun <reified T : Activity> Context.startActivity() {
    val intent = Intent(this, T::class.java)
    startActivity(intent)
}
startActivity<MainActivity>()

限制

  • reified 只能用于 inline 函数

  • 不能用于无运行时表示的类型(如普通泛型参数 TNothing

1.5 内联属性

没有幕后字段(backing field)的属性访问器也可以内联:

val foo: Foo inline get() = Foo()

inline var bar: Bar
    get() = createBar()
    set(v) { saveBar(v) }

1.6 公开 API 的内联限制

public 内联函数体内禁止引用 privateinternal 声明,因为调用方模块不会重新编译。如果确实需要,用 @PublishedApi 提升内部声明的可见性:

@PublishedApi
internal fun internalHelper() { /* ... */ }

inline fun publicInlineFunc() {
    internalHelper()  // 允许,因为标记了 @PublishedApi
}

第二部分:泛型与型变

2.1 泛型基础

Kotlin 的泛型与 Java 类似,但语法更简洁,支持类型推断:

class Box<T>(t: T) { var value = t }
val box = Box(1)  // 编译器自动推断为 Box<Int>

2.2 声明处型变:in 和 out

Kotlin 摒弃了 Java 的通配符 ? extends T / ? super T,改用声明处型变注解,在类型参数声明时就明确它是生产者还是消费者。

注解

型变类型

规则

语义

out

协变(Covariant)

只能出现在返回值位置

生产者。C<Derived>C<Base> 的子类型

in

逆变(Contravariant)

只能出现在参数位置

消费者。C<Base>C<Derived> 的子类型

协变(out)—— 生产者

interface Source<out T> {
    fun nextT(): T  // T 只出现在返回值,安全
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs  // ✅ 安全:String 是 Any 的子类型
}

逆变(in)—— 消费者

interface Comparable<in T> {
    operator fun compareTo(other: T): Int  // T 只出现在参数,安全
}

fun demo(x: Comparable<Number>) {
    val y: Comparable<Double> = x  // ✅ 安全:能比较 Number 的肯定能比较 Double
}

记忆口诀Consumer in, Producer out(消费者用 in,生产者用 out)

2.3 使用处型变:类型投影

当你不能修改类声明(如标准库的 Array<T> 同时有 getset)时,可以在使用处进行投影:

// Array<out Any>:只读投影,禁止 set
fun copy(from: Array<out Any>, to: Array<Any>) {
    for (i in from.indices) to[i] = from[i]  // from[i] 是 get,允许
    // from[i] = "x"  // ❌ 编译错误:out 投影禁止写入
}

// Array<in String>:只写投影,禁止 get
fun fill(arr: Array<in String>, value: String) {
    arr[0] = value  // 允许
    // val s: String = arr[0]  // ❌ 编译错误:in 投影禁止读取为 String
}

等价于 Java 的 Array<? extends Object> / Array<? super String>

2.4 星投影(Star Projections)

当泛型类型参数未知但仍需安全使用时,用 *

interface Foo<T> { fun get(): T; fun set(value: T) }

fun process(foo: Foo<*>) {
    // Foo<out Any?>:可以安全读取为 Any?
    val value: Any? = foo.get()
    // foo.set("x")  // ❌ 编译错误:无法安全写入
}

声明

Foo<*> 等价于

安全操作

Foo<out T : TUpper>

Foo<out TUpper>

可读取为 TUpper

Foo<in T>

Foo<in Nothing>

无法安全写入

Foo<T : TUpper>

读:Foo<out TUpper>
写:Foo<in Nothing>

读取受限,禁止写入

2.5 类型参数约束

// 单一上界
fun <T : Comparable<T>> sort(list: List<T>) { /* ... */ }

// 多个上界:使用 where
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence, T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString().uppercase() }
}

2.6 类型擦除与 reified 的配合

Kotlin 泛型的安全检查仅在编译期。运行时泛型信息会被擦除:

// ❌ 不能做运行时类型检查
if (obj is List<Int>) { /* ... */ }  // 编译错误

// ✅ 可以检查星投影
if (obj is List<*>) { /* ... */ }

// ✅ 使用 reified 保留类型信息(仅限 inline 函数)
inline fun <reified T> cast(obj: Any): T {
    return obj as T  // 编译期已知道 T 的具体类型
}

第三部分:扩展函数与属性

3.1 扩展函数

在不修改原始类的前提下添加方法:

fun String.truncate(maxLength: Int): String {
    return if (this.length <= maxLength) this else take(maxLength - 3) + "..."
}

// 调用
println("Hello World".truncate(8))  // "He..."

泛型扩展

fun <T> List<T>.endpoints(): Pair<T, T> = first() to last()

val numbers = listOf(1, 2, 3, 4, 5)
println(numbers.endpoints())  // (1, 5)

可空接收者——允许对 null 调用:

fun Any?.toSafeString(): String {
    return if (this == null) "null" else toString()
}

println(null.toSafeString())  // "null"

3.2 扩展属性

扩展属性不允许幕后字段,必须显式定义 get()set()

data class User(val firstName: String, val lastName: String)

val User.fullName: String
    get() = "$firstName $lastName"

val User.emailUsername: String
    get() = "${firstName.lowercase()}.${lastName.lowercase()}"

// 使用
val user = User("John", "Doe")
println(user.fullName)       // "John Doe"
println(user.emailUsername)  // "john.doe"

3.3 重要限制:静态分发

扩展函数是静态分发的,编译器根据声明的变量类型(而非运行时实例)决定调用哪个扩展:

open class Shape
class Rectangle : Shape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printName(s: Shape) = println(s.getName())

printName(Rectangle())  // 输出: "Shape"(不是 "Rectangle"!)

成员函数优先于扩展函数:如果类本身有同名方法,扩展函数不会被调用。

3.4 伴生对象扩展

class MyClass {
    companion object {
        // companion body
    }
}

fun MyClass.Companion.log(msg: String) {
    println("[MyClass] $msg")
}

// 调用
MyClass.log("Hello")  // 不需要实例

第四部分:委托(Delegation)

4.1 类委托(Class Delegation)

委托模式是实现继承的零样板替代方案。使用 by 子句,编译器自动生成所有接口方法的转发调用:

interface Base {
    fun print()
    fun printLine()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
    override fun printLine() { println(x) }
}

class Derived(b: Base) : Base by b

fun main() {
    val base = BaseImpl(10)
    Derived(base).print()       // 输出: 10
    Derived(base).printLine()   // 输出: 10
}

4.2 重写委托方法

class Derived(b: Base) : Base by b {
    override fun print() { print("abc") }
}

fun main() {
    val base = BaseImpl(10)
    val derived = Derived(base)
    derived.print()       // 输出: abc(使用重写实现)
    derived.printLine()   // 输出: 10(使用委托实现)
}

关键陷阱:委托对象内部调用 print() 时,不会调用派生类的重写版本。委托对象只认自己的实现。

4.3 属性委托(Delegated Properties)

属性的 get()set() 被转发到委托对象的 getValue()setValue() 方法:

class Example {
    var p: String by Delegate()
}
// 读取 e.p → Delegate.getValue(e, property)
// 赋值 e.p = "NEW" → Delegate.setValue(e, property, "NEW")

4.4 标准库内置委托

委托

作用

示例

lazy()

延迟初始化(首次访问计算并缓存)

val config by lazy { loadConfig() }

observable()

变更监听(赋值后触发)

var name by Delegates.observable("") { _, old, new -> println("$old → $new") }

vetoable()

变更拦截(赋值前触发,返回 false 否决)

var age by Delegates.vetoable(0) { _, _, new -> new >= 0 }

lazy 的三种线程安全模式

val lazy1: String by lazy { "computed" }  // 默认:线程安全(synchronized)
val lazy2: String by lazy(LazyThreadSafetyMode.PUBLICATION) { "computed" }  // 可能多次执行,首次返回值为准
val lazy3: String by lazy(LazyThreadSafetyMode.NONE) { "computed" }  // 无锁,单线程最快

4.5 基于 Map 的委托

直接将 Map 作为委托,键名自动匹配属性名,适合 JSON 解析:

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

val user = User(mapOf("name" to "Alice", "age" to 30))
println(user.name)  // "Alice"
println(user.age)   // 30

4.6 provideDelegate —— 绑定前拦截器

若委托对象定义了 provideDelegate,编译器会在创建属性前调用它,适合做校验:

class ResourceLoader<T>(val id: Int) {
    operator fun provideDelegate(thisRef: MyUI, prop: KProperty<*>): ReadOnlyProperty<MyUI, T> {
        checkResourceExists(id)  // 在绑定前校验
        return createResource(id)
    }
}

val image by ResourceLoader<ImageResource>(R.id.image_id)

第五部分:密封类与密封接口

5.1 核心概念

密封类限制继承层级,确保所有直接子类在编译期已知,配合 when 表达式可实现穷尽分支检查,无需 else

sealed class Result<out T> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Throwable) : Result<Nothing>()
    object Loading : Result<Nothing>()
}

fun handleResult(result: Result<String>): String = when (result) {
    is Result.Success -> "Data: ${result.data}"
    is Result.Error -> "Error: ${result.exception.message}"
    Result.Loading -> "Loading..."
    // 无需 else,编译器保证全覆盖
}

5.2 继承规则

  • 直接子类必须与密封类在同一文件(Kotlin 1.5+ 起同一模块即可,但推荐同文件)

  • 密封类本身隐式为 abstract,不可直接实例化

  • 构造函数只能是 protected(默认)或 private

  • enum 类不能继承密封类,但可以实现密封接口

sealed interface PaymentMethod
sealed class CardPayment : PaymentMethod {
    protected constructor()  // 默认
    private constructor(token: String) : this()  // 更严格
    // public constructor() : this()  // ❌ 编译错误
}

class CreditCard(val number: String) : CardPayment()
class DebitCard(val number: String) : CardPayment()
object Cash : PaymentMethod

// enum 可以实现密封接口
enum class DigitalPayment(val name: String) : PaymentMethod {
    APPLE_PAY, GOOGLE_PAY, SAMSUNG_PAY
}

5.3 UI 状态建模(经典场景)

sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<out T>(val data: T) : UiState<T>()
    data class Failure(val error: Throwable) : UiState<Nothing>()
}

// ViewModel 中
class UserViewModel : ViewModel() {
    private val _state = MutableStateFlow<UiState<List<User>>>(UiState.Loading)
    val state: StateFlow<UiState<List<User>>> = _state

    fun loadUsers() {
        viewModelScope.launch {
            _state.value = UiState.Loading
            try {
                val users = userRepository.getAll()
                _state.value = UiState.Success(users)
            } catch (e: Exception) {
                _state.value = UiState.Failure(e)
            }
        }
    }
}

第六部分:高阶函数与 Lambda

6.1 Lambda 表达式基础

// 完整形式
val sum: (Int, Int) -> Int = { x, y -> x + y }

// 类型推断
val sum = { x: Int, y: Int -> x + y }

// 作为参数传递
fun operate(a: Int, b: Int, op: (Int, Int) -> Int) = op(a, b)
operate(3, 4, sum)  // 7

6.2 带接收者的 Lambda(函数类型字面量)

Lambda 可以在一个接收者对象上调用,使得 this 在 Lambda 体内隐式可用。这是 DSL 构建的核心:

// 类型:String.() -> Int
val stringLength: String.() -> Int = { this.length }

println("Hello".stringLength())  // 5

// 实际应用中:
fun StringBuilder.build(init: StringBuilder.() -> Unit): StringBuilder {
    this.init()
    return this
}

val result = StringBuilder().build {
    append("Hello")
    append(" ")
    append("World")
}

6.3 标准库中的高阶函数

Kotlin 标准库提供了大量高阶函数,熟练掌握它们是写出简洁 Kotlin 代码的关键:

作用域函数

函数

this / it

返回值

适用场景

let

it(Lambda 参数)

Lambda 返回值

空安全调用、链式转换

run

this(接收者)

Lambda 返回值

对象配置 + 计算结果

with

this(接收者)

Lambda 返回值

非空对象的多步操作

apply

this(接收者)

对象本身

对象初始化/配置

also

it(Lambda 参数)

对象本身

副作用(日志、调试)

// let:空安全链式转换
val name: String? = null
val length = name?.let { it.uppercase().length }  // null

// apply:对象配置
val user = User().apply {
    firstName = "John"
    lastName = "Doe"
    age = 30
}

// also:副作用
val numbers = mutableListOf(1, 2, 3)
    .also { println("Before: $it") }
    .map { it * 2 }
    .also { println("After: $it") }

第七部分:Flow 异步流

7.1 Flow 基础

Flow<T> 是异步计算的多值序列。与 suspend 函数返回单个值不同,Flow 可以逐步发出多个值。

fun simple(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(100)  // 模拟异步操作
        emit(i)     // 发出值
    }
}

suspend fun main() {
    simple().collect { value -> println(value) }  // 1, 2, 3(间隔 100ms)
}

Flow 是冷流flow { } 构建器中的代码只有在调用 collect() 时才会执行。每次 collect 都会从头开始。

7.2 中间操作符

操作符

说明

map

转换每个元素(Lambda 可以是 suspend)

filter

过滤元素

transform

最灵活:可以emit任意多次、任意类型

take(n)

只取前 n 个

drop(n)

跳过前 n 个

distinctUntilChanged()

去重(相邻相同只发一次)

zip(other)

配对两个 Flow 的对应元素

combine(other)

任一 Flow 发出新值时,用最新值组合

// transform:一个输入产生多个输出
(1..3).asFlow().transform { request ->
    emit("Making request $request")
    emit("Result: ${request * 2}")
}.collect { println(it) }
// 输出: Making request 1, Result: 2, Making request 2, Result: 4, ...

// combine:响应式组合(常用于 UI 状态)
val flow1 = flowOf(1, 2, 3).onEach { delay(100) }
val flow2 = flowOf("A", "B").onEach { delay(150) }

flow1.combine(flow2) { num, str -> "$num$str" }
    .collect { println(it) }
// 输出: 1A, 2A, 2B, 3B

7.3 终止操作符

终止操作符是 suspend 函数,调用后才会启动 Flow 的执行:

// collect:最基本的终止操作符
flow.collect { println(it) }

// toList / toSet:收集为集合
val list = flow.toList()

// first / single:取第一个/唯一的元素
val first = flow.first()

// reduce / fold:聚合
val sum = flowOf(1, 2, 3, 4, 5).reduce { a, b -> a + b }  // 15
val folded = flowOf(1, 2, 3).fold("") { acc, v -> acc + v }  // "123"

7.4 异常处理

fun numbers(): Flow<Int> = flow {
    for (i in 1..5) {
        if (i == 3) throw RuntimeException("Error at 3")
        emit(i)
    }
}

// 方式一:try/catch
try {
    numbers().collect { println(it) }
} catch (e: Exception) {
    println("Caught: $e")
}

// 方式二:catch 操作符(推荐,只捕获上游异常)
numbers()
    .catch { e -> println("Caught: $e") }
    .collect { println(it) }

// catch 可以 emit 回退值
numbers()
    .catch { e -> emit(-1) }
    .collect { println(it) }  // 1, 2, -1

7.5 背压与并发控制

Flow 默认是顺序执行的(发射和收集在同一协程中)。当发射快于收集时:

// buffer():发射器和收集器并发运行
flow
    .buffer()
    .collect { delay(300); println(it) }

// conflate():慢消费者跳过中间值,只保留最新
flow
    .conflate()
    .collect { delay(300); println(it) }

// collectLatest():每次新值到达时取消之前的处理
flow
    .collectLatest { value ->
        delay(300)  // 如果新值在 300ms 内到达,此延迟会被取消
        println(value)
    }

7.6 flowOn —— 切换发射上下文

fun numbers(): Flow<Int> = flow {
    println("Flow started on ${Thread.currentThread().name}")
    for (i in 1..3) {
        delay(100)
        emit(i)
    }
}.flowOn(Dispatchers.IO)  // 上游在 IO 线程执行

// collect 仍在调用者的上下文执行
numbers().collect { println("Collected $it on ${Thread.currentThread().name}") }

7.7 StateFlow —— 状态共享的热流

StateFlow 是 Flow 的状态持有者版本,类似 LiveData:

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState

    fun loadUser() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                val user = userRepository.fetchUser()
                _uiState.value = UiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e)
            }
        }
    }
}

// Compose 中收集
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    when (uiState) {
        is UiState.Loading -> Loading()
        is UiState.Success -> Content((uiState as UiState.Success).user)
        is UiState.Error -> Error((uiState as UiState.Error).message)
    }
}

7.8 SharedFlow —— 事件广播

SharedFlow 适合一对多事件广播(如通知、Toast):

class NotificationManager {
    private val _events = MutableSharedFlow<Notification>(extraBufferCapacity = 10)
    val events: SharedFlow<Notification> = _events

    suspend fun send(notification: Notification) {
        _events.emit(notification)
    }
}

// 多个订阅者各自收到完整的事件流
lifecycleScope.launch {
    notificationManager.events.collect { showNotification(it) }
}

第八部分:协程高级用法

8.1 launch vs async

构建器

返回值

适用场景

launch

Job

后台任务,不需要结果(Fire-and-forget)

async

Deferred<T>

并发计算,需调用 .await() 获取结果

suspend fun main() = coroutineScope {
    // launch:不阻塞当前作用域
    val job = launch {
        delay(1000)
        println("Background task done")
    }
    println("Continues immediately")
    job.join()  // 等待完成

    // async:并行获取多个结果
    val page1 = async { fetchPage(1) }
    val page2 = async { fetchPage(2) }
    val allContent = page1.await() + page2.await()
}

8.2 结构化并发

协程在 CoroutineScope 中运行,遵循结构化并发原则:

  • 父协程等待所有子协程完成

  • 父协程取消 → 子协程级联取消

  • 保证资源安全释放

suspend fun main() {
    coroutineScope {
        launch {
            launch {
                delay(2000)
                println("Nested child completed")
            }
            println("Child 1 started")
        }
        launch {
            delay(1000)
            println("Child 2 completed")
        }
    }
    println("All children completed")  // 最后执行
}

8.3 取消与超时

// 取消
val job = launch {
    repeat(1000) { i ->
        println("Working $i")
        delay(100)
        // 协程内部自动检查取消状态
        ensureActive()  // 手动检查(可选)
    }
}
delay(500)
job.cancel()  // 请求取消
job.join()    // 等待取消完成
println("Cancelled")

// 超时
withTimeout(1000L) {
    repeat(1000) { i ->
        println("Working $i")
        delay(200)
    }
}  // 超时抛出 TimeoutCancellationException

// 超时不抛异常,返回 null
val result = withTimeoutOrNull(1000L) {
    doSomething()
}
if (result == null) println("Timed out")

8.4 CoroutineContext 与 Dispatcher

suspend fun main() {
    // 切换执行上下文
    withContext(Dispatchers.IO) {
        // 数据库/网络 I/O
    }

    withContext(Dispatchers.Default) {
        // CPU 密集型计算
    }

    // 自定义线程池
    val dispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
    withContext(dispatcher) {
        // 在自定义线程池执行
    }
}

8.5 Channel —— 协程间通信

当需要在协程之间传递数据流时,使用 Channel:

suspend fun main() = coroutineScope {
    val channel = Channel<Int>()

    // 生产者
    launch {
        for (i in 1..5) {
            channel.send(i * i)
            delay(100)
        }
        channel.close()  // 通知消费者不再有数据
    }

    // 消费者
    launch {
        for (value in channel) {
            println("Received: $value")
        }
        println("Channel closed")
    }
}

Channel 的变体:

类型

说明

Channel()

Rendezvous Channel(无缓冲,send 和 receive 必须配对)

Channel(Channel.UNLIMITED)

无界缓冲,send 从不挂起

Channel(Channel.CONFLATED)

只保留最新值,旧值丢弃

Channel(capacity = N)

固定容量缓冲

8.6 协程异常处理

// launch 中的异常:默认取消父作用域
val job = GlobalScope.launch {
    try {
        doSomethingRisky()
    } catch (e: Exception) {
        // 处理异常,防止取消传播
    }
}

// 使用 CoroutineExceptionHandler
val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught: $exception")
}
val job = GlobalScope.launch(handler) {
    throw RuntimeException("Error")
}

// SupervisorJob:子协程异常不影响兄弟协程
val scope = CoroutineScope(SupervisorJob())
scope.launch { taskA() }  // 失败不影响 taskB
scope.launch { taskB() }

第九部分:类型安全 DSL 构建

9.1 核心原理

Kotlin 的 DSL 能力来自带接收者的 LambdaReceiver.() -> Unit)。调用 Lambda 时,接收者成为隐式的 this

class HTML {
    val children = mutableListOf<Tag>()
    fun body(init: Body.() -> Unit) {
        val body = Body()
        body.init()
        children.add(body)
    }
}

class Body {
    val children = mutableListOf<Tag>()
    fun p(init: P.() -> Unit) {
        val p = P()
        p.init()
        children.add(p)
    }
}

class P {
    var text = ""
}

// DSL 构建器函数
fun html(init: HTML.() -> Unit): HTML {
    val html = HTML()
    html.init()
    return html
}

// 使用
val result = html {
    body {
        p { text = "Hello World" }
    }
}

9.2 @DslMarker —— 作用域控制

在嵌套 DSL 中,防止误调用外层接收者的方法:

@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class HtmlTagMarker

@HtmlTagMarker abstract class Tag

@HtmlTagMarker class HTML : Tag()
@HtmlTagMarker class Body : Tag()
@HtmlTagMarker class P : Tag()

// 现在:
html {
    body {
        // body { }  // ❌ 编译错误:不允许访问外层 Body 接收者
        p { }       // ✅ 当前作用域内的方法
    }
}

9.3 Operator 重载在 DSL 中的应用

abstract class TagWithText(name: String) : Tag(name) {
    // 重载一元 + 操作符,用于添加纯文本
    operator fun String.unaryPlus() {
        children.add(TextElement(this))
    }
}

// DSL 中使用
p {
    +"This is some "
    b { +"bold" }
    +" text."
}

9.4 实战:构建一个 Kotlin DSL 配置文件

data class ServerConfig(
    val host: String,
    val port: Int,
    val ssl: Boolean,
    val endpoints: List<Endpoint>
)

data class Endpoint(val path: String, val method: String)

class ServerConfigBuilder {
    var host: String = "localhost"
    var port: Int = 8080
    var ssl: Boolean = false
    private val endpoints = mutableListOf<Endpoint>()

    fun endpoint(path: String, method: String = "GET") {
        endpoints.add(Endpoint(path, method))
    }

    fun build() = ServerConfig(host, port, ssl, endpoints.toList())
}

fun server(init: ServerConfigBuilder.() -> Unit): ServerConfig {
    val builder = ServerConfigBuilder()
    builder.init()
    return builder.build()
}

// 使用
val config = server {
    host = "api.example.com"
    port = 443
    ssl = true
    endpoint("/users", "GET")
    endpoint("/users", "POST")
    endpoint("/users/{id}", "PUT")
    endpoint("/users/{id}", "DELETE")
}

第十部分:Operator 重载与中缀调用

10.1 Operator 重载

Kotlin 允许为自定义类型重载约定的操作符:

data class Point(val x: Int, val y: Int) {
    // 二元操作符:+
    operator fun plus(other: Point) = Point(x + other.x, y + other.y)

    // 二元操作符:-
    operator fun minus(other: Point) = Point(x - other.x, y - other.y)

    // 一元操作符:负号
    operator fun unaryMinus() = Point(-x, -y)

    // 复合赋值:+=
    operator fun inc() = Point(x + 1, y + 1)

    // 索引访问:[]
    operator fun get(index: Int) = when (index) {
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException()
    }

    // 包含:in
    operator fun contains(point: Point) = x == point.x && y == point.y

    // 范围:..
    operator fun rangeTo(other: Point) = PointRange(this, other)

    // 调用:()
    operator fun invoke(scale: Int) = Point(x * scale, y * scale)
}

// 使用
val p1 = Point(1, 2)
val p2 = Point(3, 4)

println(p1 + p2)     // Point(4, 6)
println(p1 - p2)     // Point(-2, -2)
println(-p1)         // Point(-1, -2)
println(p1[0])       // 1
println(p2 in p1)    // false
println(p1(3))       // Point(3, 6)

常用可重载操作符

表达式

对应方法

说明

a + b

a.plus(b)

加法/连接

a - b

a.minus(b)

减法

a * b

a.times(b)

乘法

a / b

a.div(b)

除法

a % b

a.rem(b)

取余

a++

a.inc()

自增

a--

a.dec()

自减

+a

a.unaryPlus()

正号

-a

a.unaryMinus()

负号

!a

a.not()

a == b

a.equals(b)

相等

a > b

a.compareTo(b) > 0

比较

a[i]

a.get(i)

索引读取

a[i] = v

a.set(i, v)

索引写入

a in b

b.contains(a)

包含

a..b

a.rangeTo(b)

范围

a()

a.invoke()

调用

a += b

a.plusAssign(b)

复合赋值

10.2 中缀调用(Infix Calls)

使用 infix 关键字让函数以无括号、无点的方式调用:

class BitArray(val bits: IntArray) {
    infix fun shl(n: Int): BitArray {
        return BitArray(bits.map { it shl n }.toIntArray())
    }

    infix fun shr(n: Int): BitArray {
        return BitArray(bits.map { it shr n }.toIntArray())
    }

    infix fun and(other: BitArray): BitArray {
        return BitArray(bits.zip(other.bits).map { (a, b) -> a and b }.toIntArray())
    }

    infix fun or(other: BitArray): BitArray {
        return BitArray(bits.zip(other.bits).map { (a, b) -> a or b }.toIntArray())
    }
}

// 使用中缀调用
val a = BitArray(intArrayOf(1, 0, 1))
val b = BitArray(intArrayOf(0, 1, 1))
val result = a shl 2 and b or a  // 链式中缀调用

// 标准库中的中缀调用
val map = mapOf(1 to "one", 2 to "two")  // to 是中缀函数
val filtered = list filter { it > 0 }    // filter 可以作为中缀

规则:中缀函数必须是成员函数扩展函数,且只能有一个参数


第十一部分:综合实战

11.1 类型安全 API 客户端

结合 reified、密封类、扩展函数、内联函数构建一个响应式 API 客户端:

// 密封类建模 API 响应
sealed class ApiResponse<out T> {
    data class Success<out T>(val data: T) : ApiResponse<T>()
    data class Error(val code: Int, val message: String) : ApiResponse<Nothing>()
    object Loading : ApiResponse<Nothing>()
}

// reified 类型安全的 JSON 解析
inline fun <reified T> String.parseJson(): T {
    return ObjectMapper().readValue(this, object : TypeReference<T>() {})
}

// 扩展函数:安全执行 API 调用
suspend fun <T> suspendCatching(block: suspend () -> T): ApiResponse<T> = try {
    ApiResponse.Success(block())
} catch (e:HttpException) {
    ApiResponse.Error(e.code(), e.message() ?: "HTTP Error")
} catch (e: Exception) {
    ApiResponse.Error(-1, e.message ?: "Unknown error")
}

// 内联函数:带重试的 API 调用
inline fun <reified T> withRetry(
    maxRetries: Int = 3,
    crossinline block: suspend () -> T
): suspend () -> ApiResponse<T> = {
    repeat(maxRetries) { attempt ->
        val result = suspendCatching { block() }
        if (result is ApiResponse.Success) return@withRetry result
        if (attempt < maxRetries - 1) delay(1000L * (attempt + 1))
    }
    ApiResponse.Error(-1, "Failed after $maxRetries retries")
}

// 使用
val apiCall = withRetry(maxRetries = 3) {
    httpClient.get("/api/users/1").parseJson<User>()
}
val response = apiCall()

11.2 类型安全数据库查询 DSL

@DslMarker
annotation class QueryDsl

@QueryDsl
class QueryBuilder {
    private val columns = mutableListOf<String>()
    private val tables = mutableListOf<String>()
    private var whereClause: String? = null
    private var orderBy: String? = null
    private var limit: Int? = null

    fun select(vararg cols: String) {
        columns.addAll(cols)
    }

    fun from(vararg tables: String) {
        this.tables.addAll(tables)
    }

    infix fun String.eq(value: Any) = "$this = '${value.toString().escapeSql()}'"
    infix fun String.gt(value: Any) = "$this > '${value.toString().escapeSql()}'"
    infix fun String.like(pattern: String) = "$this LIKE '$pattern'"

    fun where(condition: String) {
        whereClause = condition
    }

    fun orderBy(column: String, desc: Boolean = false) {
        orderBy = "$column ${if (desc) "DESC" else "ASC"}"
    }

    fun limit(n: Int) { limit = n }

    fun build(): String {
        val col = columns.joinToString(", ").takeIf { it.isNotEmpty() } ?: "*"
        var sql = "SELECT $col FROM ${tables.joinToString(", ")}"
        whereClause?.let { sql += " WHERE $it" }
        orderBy?.let { sql += " ORDER BY $it" }
        limit?.let { sql += " LIMIT $it" }
        return sql
    }
}

fun query(init: QueryBuilder.() -> Unit): String {
    val builder = QueryBuilder()
    builder.init()
    return builder.build()
}

// 使用
val sql = query {
    select("id", "name", "email")
    from("users")
    where ("age" gt 18 and "status" eq "active")
    orderBy("name")
    limit(10)
}
// SELECT id, name, email FROM users WHERE age > '18' AND status = 'active' ORDER BY name ASC LIMIT 10

第十二部分:常见问题 FAQ

Q1: inline 函数一定会提升性能吗?

不一定。内联会增大字节码体积。只有小函数 + 频繁调用的场景(如标准库的 letrunapply)才有明显的性能提升。大型函数内联后反而可能导致指令缓存命中率下降。

Q2: reified 为什么只能在 inline 函数中使用?

因为泛型擦除发生在运行期。reified 的本质是编译器在内联展开调用点时将具体的类型信息直接写入字节码。如果不内联,运行期就无法获取类型信息。

Q3: 扩展函数能访问接收者的 private 成员吗?

不能。扩展函数不是类的真正成员,它只是编译器语法糖。只能访问 publicinternalprotected(如果是子类)成员。

Q4: lazy() 线程安全吗?

默认是线程安全的(使用 synchronized)。如果确认单线程使用,可以用 lazy(LazyThreadSafetyMode.NONE) 提升性能。

Q5: Flow 和 Channel 该选哪个?

  • Flow:一对多的异步数据流,适合生产者-消费者模型中的"数据查询"场景

  • Channel:一对一的协程间通信管道,适合"消息传递"场景

  • 大多数业务场景用 Flow 就够了。Channel 更多用于底层并发控制

Q6: 密封类和枚举类该选哪个?

  • 枚举:每个常量只有一个实例,不能携带状态

  • 密封类:每个子类可以有不同的属性、方法、状态

  • 如果只需要一组命名常量 → 枚举

  • 如果每个变体需要携带不同的数据 → 密封类

Q7: Kotlin 的 in/out 和 Java 的 ? extends / ? super 是一回事吗?

概念相同,但 Kotlin 的方式更安全、更简洁。Java 的 ? extends T 等同于 Kotlin 的 out T 投影,? super T 等同于 in T 投影。区别在于 Kotlin 在声明处就标注了型变,而不是每次使用时都写通配符。


附录:速查表

标准库委托

val lazyVal by lazy { compute() }
var observable by Delegates.observable(0) { _, old, new -> }
var vetoable by Delegates.vetoable(0) { _, _, new -> new > 0 }
val fromMap by map  // Map<String, Any?>
var byRef by ::otherProperty

型变速记

interface Producer<out T> { fun produce(): T }     // 只产出 → out
interface Consumer<in T> { fun consume(t: T) }     // 只消费 → in
interface Both<T> { fun get(): T; fun set(t: T) }  // 两者都有 → 不变

Flow 操作符分类

// 中间操作符(返回 Flow)
.map { }.filter { }.transform { }.take(n).drop(n)
.distinctUntilChanged().zip(other).combine(other)
.catch { }.retry(n).flowOn(dispatcher).buffer()

// 终止操作符(suspend 函数)
.collect { }.toList().toSet().first().single()
.reduce { }.fold(initial) { }

总结

Kotlin 的高级特性构成了一个有机的整体:

  • 内联 + reified → 零开销抽象 + 运行时类型信息

  • 泛型 + 型变 → 类型安全的容器与集合操作

  • 扩展 + 委托 → 无侵入的代码增强

  • 密封类 + when → 编译期穷尽检查的分支控制

  • Flow + 协程 → 优雅的异步数据流处理

  • DSL 构建 + Operator 重载 → 领域特定的表达力

掌握这些,你才能真正写出"Kotlin 风格"的代码——简洁、安全、表达力强。