1. 扩展方法
什么是扩展方法?
Kotlin 的扩展方法(Extension Function)是一种在 不修改类源码、不继承该类 的情况下,为现有类“添加”新函数的机制。
fun String.lastChar(): Char {
return this[this.length - 1]
}
fun main() {
println("Kotlin".lastChar()) // 输出 'n'
}
需要注意的是,扩展方法 并没有真正修改类本身。它在 编译期 会被静态解析成一个 静态方法调用,调用对象(Receiver)作为第一个参数传入。所以扩展方法只是语法糖,不是真正的类方法。编译后的 Java 代码大致是:
public static final char lastChar(@NotNull String $this$lastChar) {
Intrinsics.checkNotNullParameter($this$lastChar, "<this>");
return $this$lastChar.charAt($this$lastChar.length() - 1);
}
扩展方法和类的成员方法同名怎么办?
如果扩展方法和类的成员方法 同名且签名相同,那么 成员方法优先。
class Student() {
fun study() {
println("study...")
}
}
// 扩展方法与成员方法签名相同,成员方法优先
fun Student.study() {
println("sleep...")
}
// 签名不同,不会被屏蔽
fun Student.study(book: String) {
println("study $book")
}
fun main() {
val student = Student()
student.study() // study...
student.study("Android") // study Android
}
父类的扩展方法子类可以调用吗?
可以使用。原因是扩展方法在本质上是一个静态函数,Receiver 作为第一个参数传入,因此子类对象同样可以传进去使用。
例如 Kotlin 为 CharSequence
定义了扩展方法 isEmpty()
,而 String
作为子类自然也能调用:
public inline fun CharSequence.isEmpty(): Boolean = length == 0
fun main() {
val str: String = ""
println(str.isEmpty()) // true
}
2. 高阶函数
什么是高阶函数?
高阶函数是指:可以接收函数作为参数,或者返回一个函数作为结果的函数。在 Kotlin 中,函数可以像对象一样传递、存储和返回。高阶函数的强大之处在于它们常常与 Lambda 表达式 一起使用,使得代码更加简洁。
常见场景:
函数式编程风格:集合操作
list.map{ }
回调函数(Callback):
button.setOnClickListener { println("clicked") }
fun operate(x: Int, y: Int, op: (Int, Int) -> Int): Int {
return op(x, y)
}
fun main() {
val sum = operate(3, 5) { a, b -> a + b }
println(sum) // 输出 8
}
在 JVM 上,Kotlin 的高阶函数其实是通过 函数类型对象 来实现的。
在 Kotlin 中
(Int) -> String
这种函数类型,本质上是编译成kotlin.jvm.functions.FunctionN
接口 的匿名类。FunctionN
是一组接口,比如:Function0<R>
:无参函数Function1<P1, R>
:一个参数Function2<P1, P2, R>
:两个参数以此类推,最多支持
Function22
。
上面代码中的 opreate()
函数编译后的内容大致如下,函数参数 op
实际上是 Function2
接口的实例。
public static final int operate(int x, int y, @NotNull Function2 op) {
Intrinsics.checkNotNullParameter(op, "op");
return ((Number) op.invoke(x, y)).intValue();
}
Kotlin 的高阶函数是通过 函数类型 编译成 FunctionN
接口的实现来完成的,Lambda 会被编译成实现了 FunctionN
接口的匿名类对象。如果 Lambda 捕获了外部变量,就会生成一个闭包类。为了避免性能开销,Kotlin 提供了 inline
,在编译期直接内联代码,从而减少对象分配和调用开销。
几个有用的高阶函数
Kotlin 官方内置了很多有用的高阶函数,下面是他们的源码:
// 执行一段映射,返回 R,T 作为参数传递至 block
public inline fun <T, R> T.let(block: (T) -> R): R
// 执行一段映射,返回 R, Receiver (this) 传递至 block
public inline fun <T, R> T.run(block: T.() -> R): R
// 执行一段逻辑,T 作为参数传递至 block
public inline fun <T> T.also(block: (T) -> Unit): T
// 执行一段逻辑,T 作为 Receiver (this) 传递至 block
public inline fun <T> T.apply(block: T.() -> Unit): T
// 重复执行 action 多次
public inline fun repeat(times: Int, action: (Int) -> Unit)
使用实例:
val s = "abc"
// s 作为 Lambda 的参数传递
val r1 = s.let { it ->
println(it.length)
it.substring(2)
}
println("r1 = ${r1}")
// s 作为 this 传递,可以直接操作属性和方法
val r2 = s.run {
println(this)
println(length)
substring(1)
}
println("r2 = ${r2}")
// s 作为参数传递,返回值为 Unit
s.also { it->
println("also, ${it}, ${it.length}")
}
// s 作为 this 传递,返回值为 Unit
s.apply {
println("apply, $length")
}
3. 内联函数
在 Kotlin 中,可以使用 inline
关键字将函数声明为内联函数。所谓内联,是指在 编译期 编译器会进行“代码展开”,具体包括两方面:
Kotlin 中使用 inline
关键字修饰的函数是内联函数。在编译时会将:
函数本身的调用会被内联到调用处,避免函数调用的栈开销;
函数参数(尤其是 Lambda 表达式)也会被内联到调用处,避免生成额外对象,从而提升性能。
例如,Kotlin 标准库中的 forEach
就是一个典型的内联函数:
public inline fun IntArray.forEach(action: (Int) -> Unit): Unit {
for (element in this) action(element)
}
当调用 forEach
时,编译器会直接将传入的 Lambda 代码“拷贝”到循环体中,而不是生成额外的方法调用。因此,内联在高阶函数(接收 Lambda 参数的函数)中尤为常用。
内联高阶函数的 return
在内联高阶函数中,return
的行为有一些特殊之处。比如在 forEach
中,无法实现 break
的效果(直接跳出整个循环),只能实现 continue
的效果(跳过当前元素,继续下一次循环)。
val a = intArrayOf(1, 2, 3, 4)
a.forEach {
if (it == 3) {
return@forEach // 局部返回,相当于 continue
}
println(it)
}
等价于传统的循环写法:
for (element in a){
if (element == 3){
continue
}
println(element)
}
这里的 return@forEach
被称为 标签返回(labeled return),它只跳过当前这次 Lambda 的执行,而不会终止整个外层函数。
4. 泛型
5. 反射
6. 注解
7. 协程基础
创建协程
先来看一个简单的示例代码:
fun main() {
/**
* 创建协程
* 一个 suspend Lambda 可以通过 createCoroutine 方法创建一个协程
*/
val continuation = suspend {
println("suspend 匿名函数")
fun1()
5
}.createCoroutine(object : Continuation<Int> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result<Int>) {
println("协程执行完毕,result = $result")
}
})
/**
* 启动协程
*/
continuation.resume(Unit)
}
suspend fun fun1() = suspendCoroutine<Unit> {
thread {
Thread.sleep(1000)
println("suspend fun1")
it.resume(Unit)
}
}
在这个例子中,我们通过 createCoroutine
创建了一个协程。它接收一个 Continuation<T>
参数,用来接收协程执行完毕的结果,并返回一个 Continuation<Unit>
,我们可以通过调用它的 resume(Unit)
来启动协程。
源码中 createCoroutine
的定义如下:
public fun <T> (suspend () -> T).createCoroutine(
completion: Continuation<T>
): Continuation<Unit>
可以看到,这个函数是 suspend function
的扩展函数,作用就是将 suspend { ... }
这样的协程体编译成协程框架可识别的对象。
挂起与恢复
我们通过一个 suspend Lambda 去创建一个协程,这个 suspend Lambda 就是协程具体执行的逻辑,或者称之为协程体。
suspend { ... }
其实会被编译成一个 SuspendLambda
的实现类(位于 kotlin.coroutines.jvm.internal
包),它继承自 ContinuationImpl
(基类是 BaseContinuationImpl
),并实现了核心的 invokeSuspend()
方法。
这个方法就是协程的“状态机”,它会根据 label
字段决定从哪一步继续执行:
int label;
public final Object invokeSuspend(Object $result) {
Object suspended = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
ResultKt.throwOnFailure($result);
System.out.println("suspend 匿名函数");
Continuation cont = (Continuation)this;
this.label = 1;
if (StartCoroutineKt.fun1(cont) == suspended) {
return suspended; // 挂起
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return Boxing.boxInt(5);
}
可以看到:
label = 0
:表示初次进入协程,执行第一段逻辑,遇到fun1()
时可能挂起。label = 1
:表示从fun1()
恢复后继续往下执行。每一个
suspend
调用点,都会编译成一个case
,挂起点就是switch
状态机的跳转点。
为什么能恢复?
因为挂起函数本质上接收一个 Continuation
参数,当 fun1
内部调用 it.resume(Unit)
时,会触发外部 Continuation
的 resumeWith
,重新进入 invokeSuspend()
,并根据 label
跳到上次挂起的位置。
Kotlin 的基类 BaseContinuationImpl
就实现了这种恢复逻辑:
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
public final override fun resumeWith(result: Result<Any?>) {
var current = this
var param = result
while (true) {
probeCoroutineResumed(current)
with(current) {
val completion = completion!!
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param) // 继续执行状态机
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted()
if (completion is BaseContinuationImpl) {
// 尾递归优化,继续向上层恢复
current = completion
param = outcome
启动协程
回到最开始的 createCoroutine
。它返回的其实是一个 SafeContinuation
,这是对 suspend { ... }
编译后实现类的装饰。SafeContinuation
内部保存了一个 delegate
,就是前面提到的 SuspendLambda
实现类。
当我们调用 resume(Unit)
时,会进入 SafeContinuation.resumeWith()
:
public actual override fun resumeWith(result: Result<T>) {
while (true) { // lock-free loop
val cur = this.result // atomic read
when {
cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return
cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)) {
delegate.resumeWith(result) // 启动协程体
return
}
else -> throw IllegalStateException("Already resumed")
}
}
}
也就是说:
首次调用时,
SafeContinuation
会把调用转发给delegate
,也就是SuspendLambda
的resumeWith
。后者再调用
invokeSuspend()
,从状态机的 case 0 开始执行协程体。如果中途遇到挂起点,就返回
COROUTINE_SUSPENDED
并保存状态;恢复时再继续执行下一个 case。
到这里,Kotlin 协程的 “创建 → 挂起 → 恢复 → 启动” 的完整闭环就串起来了。