一、示例代码
先来看一段示例代码,初步了解 Receiver 是什么
1 | import java.lang.StringBuilder |
上述代码可以为 String 增加一个扩展函数 buildString,可以直接在 buildString {} 作用域中使用 StringBuilder 的 append 或其他方法,省去了我们自己 new StringBuilder 的过程
下面是对代码的详细解释
- 形如 T.() -> R 的函数,就是 Receiver 类型
- 需要通过调用 [T的对象].() 的方式,来把 [T的对象] 作为 this 传到 lambda 作用域中
- 调用扩展函数时,可以显式用 this 访问传进来的对象
- 也可隐式调用
二、上述代码翻译成 java 是什么样的?
通过 jadx 工具将 class 转为 java后,代码如下
- main 方法中原本的 lambda 被转为了
buildString("", ReceiverKt$main$1.INSTANCE)
,ReceiverKt$main$1 是什么我们稍后分析 - buildString 方法接收了一个 Function1 类型的对象,证明 ReceiverKt$main$1 是 kt 帮我们生成的并且实现了 Function1
- buildString 内部逻辑就是创建了一个 StringBuilder,然后调用 function1.invoke(sb)
下面来看看 ReceiverKt$main$1 是什么
很简单,就是通过 invoke 调用我们 buildString lambda 里的内容
三、另一个代码示例
下面我们来看一段和上面例子很像的代码1
2
3
4
5
6
7
8
9
10
11
12
13fun main() {
println("".buildString_ {sb: StringBuilder ->
sb.append("d")
sb.append("e")
sb.append("f")
})
}
fun String.buildString_(action: (stringbuilder: StringBuilder) -> Unit): String {
val sb = StringBuilder()
action(sb)
return sb.toString()
}
和第一段代码的区别是
- buildString 参数变为了 (StringBuilder) -> Unit,我们将 StringBuilder 对象作为 lambda 的参数传入了
- 调用的地方需要使用 it 或者自己定义一个参数名来使用 StringBuilder 对象
这段代码转为 Java 后是什么样的呢?
可以看到使用 Receiver 的方式和使用给 lambda 函数定义参数的方式转为 Java 代码后只是在 invoke 参数的变量名有区别,仅此而已。
四、小结
- 定义 lambda Receiver,调用方可以在 lambda 作用域中使用 this 访问 Receiver 对象
- 定义 lambda 带参函数,调用方可以在 lambda 作用域中自己定义对象变量名或者使用默认的 it
- 上述两种方式转为 Java 并没有什么本质区别,只是变量名上的不同
五、kotlin 标准库里的操作符哪些用到了 Receiver?
在了解 Receiver 的原理后,可以让我们更好地理解 kotlin 标准库里的操作符
1.apply
1 | internal.InlineOnly . |
使用 apply 后,我们可以在 lambda 使用 this,比如1
2
3
4val str = "abc"
str.apply {
println(this.length)
}
2.with
1 | internal.InlineOnly . |
with 和 apply 的区别是,需要把 receiver 显示地传给 with,比如
1 | val str = "abc" |
如需转载,请注明原文出处,谢谢!