视频先行
哔哩哔哩
YouTube
下面是视频内容的脚本文案原稿分享。
小剧场
朱凯:「比如,user.name
如果可以直接写成 name
,那么这个不用写的 user
就是 Kotlin 的 implicit receiver。」
对手:「那不就是 this
?」
朱凯:「不是不是,this
是个传统概念,Java 里面就有。implicit receiver 是个新概念,这俩不一样。」
对手:「哪不一样?」
朱凯:「比如说吧」,(逐渐愣住……)「哎?」
开场
大家好,我是扔物线朱凯。
今天咱说说 Kotlin 的 implicit receiver。这是一个我们写 Kotlin 经常会用的东西,虽然你可能都没听过这个词,但你一定用过它。Kotlin 的很多高级功能,都利用到了这个概念——比如协程,协程是重度依赖它的,非常重。所以,弄明白它是个什么、怎么用、怎么去发挥它最大的价值,对我们的能力提升是非常有帮助的。
定义:其实就是 this
我们从它的定义说起。它的名字 implicit receiver,直接翻译到中文的话,叫隐式的接收器或者说接收者。啥叫「接收」啊?所谓的接收,其实指的就是接收调用,或者说接受调用。接受函数的调用啊,接受属性的访问啊。比如这个 user.name
:
user.name
左边的 user
就是它的 receiver。谁的 receiver?对于 name
的访问的 receiver。
而 implicit receiver,隐式的 receiver,指的就是不用写也自动存在的 receiver。也就是如果我把这个 user.
给删了,它依然能取到某个 User
对象的 name
:
name
那么这个隐式地被应用的 User
对象,就是对这个 name
的访问的 implicit receiver,隐式的 receiver。
这就是 implicit receiver 的定义。
不过,咱把脑子转个弯想一下,这其实就是啥?就是 this
呗?对吧?
所谓的 implicit receiver,其实就是指的这个 this
。
但 Java 里却没有隐式 receiver 这个概念,这是在 Kotlin 才增加了的概念。为啥呢?因为 Java 里的 this
很简单,就叫 this
就行了,不需要额外的专用名字;而 Kotlin 对它进行了一些关键的拓展,在拓展的同时,为了方便描述和沟通,就也给它起了专属的名字:implicit receiver。
那么它做了什么关键拓展呢?咱来从它的基本特性说起。
嵌套的 implicit receiver
this
,或者说隐式的 receiver,是可以嵌套的,比如在 Java 里我们可以这么写:
我在这个内部类的里面,想访问内部类和外部类的成员都是可以的,是吧:
这个 innerInt
是 InnerClass
里的,所以它等价于加上 this
的写法:
而下面的 outerInt
属于外面的 OuterClass
,但为了避免歧义,Java 不允许我们直接写 this
:
而需要显式地加上 OuterClass
的前缀:
而上面的 innerInt
如果展开,前缀是 InnerClass
:
也就是说,在内部类的里面,我是有内部类和外部类的双重 this
的。对吧?
另外,对于它们同名的成员变量或者方法,如果我也省略掉 this
:
拿到的就是内部类的成员。如果想拿外部类的,就必须把 this
写完整:
到现在为止,做 Java 的基本是都懂的。我们继续。
在 Kotlin 里,也是一样的逻辑。只不过写法稍微变了一下:
所以,Java 和 Kotlin 不仅都有 implicit receiver,而且也都是能嵌套的,同一个方法里可以有多个 this
,或者说多个 implicit receiver。对吧?
这是基本概念。
Kotlin 增加的 implicit receiver 嵌套:通过函数的 receiver 指定
然后,Kotlin 对于这种嵌套,又新增了一类场景——咱刚才看的是通过内部类来嵌套是吧?Kotlin 让我们还可以直接通过函数来嵌套新的 this
。比如你有一个在类型内部声明的扩展函数:
——这种函数叫 member extension function,成员扩展函数,其实就是字面意思:它既是成员函数又是扩展函数,对吧?
这种「成员扩展函数」有一个问题:一方面,因为它是 Int
的扩展函数,所以你需要对 Int
类型的对象才能调用它;但同时,它也是 IntMultiplier
的成员函数,所以你还要求你对 IntMultiplier
对象调用它:
也就是说,这里需要的是个双重 receiver:既要这个直接的 Int
,又要那个外部的 IntMultiplier
,缺一不可。——那我到底对谁调用?
Java 没有扩展函数的概念,所以不存在这种写法,但 Kotlin 是可以的。Kotlin 提供的解法是,你专门创建一个函数,并给它设置一个函数类型的参数:
函数不用做什么特别的事,关键是执行一下它的那个函数类型的参数:
另外,你要给这个函数类型的参数,设置一个 receiver 的类型:
这么一指定,就把参数的函数体内部——注意,是这个 block
的函数体,不是外部函数本身的函数体——在它内部强行安插了一个隐式的 receiver。换句话说,我在调用这个外部函数的时候,它的函数类型的参数的大括号里就有一个 IntMultiplier
类型的 this
了:
那么,我在里面就可以这么写了:
哎,就这么通过给参数设置 receiver 的方式,我强行插了一层 this
,不用写内部类也实现了这种「双重 this
」的环境,是吧?
但是需要注意,这个 this
它也不是从空气里蹦出来的:当我们这么声明 block
参数的时候,就只有对 IntMultiplier
类型的对象才能调用它。不过咱这个例子里,外部函数正好也是在 IntMultiplier
类里声明的,所以直接写就行:
但这种结构并不是必须的,你也可以用你能想到的其他方式去写这种安插。比如我可以直接给 Int
写个扩展函数,去插入一个 Int
类型的 this
:
那么我就能把里面这层 this
也做成隐式的了:
或者我如果不想写成扩展函数,我想把 Int
对象放在参数里来提供,也是行的:
只要把调用的格式对应地调整一下就可以了:
写法多种多样,但套路是一样的,对吧?
通过这种写法,我们就可以任意地往代码里插入我们指定的 implicit receiver,或者说指定的 this
,去应对「多个 this
」的需求场景了。
而且实际上,Kotlin 已经给我们提供了一套通用的函数。比如我例子里的代码,其实可以直接换成 apply()
和 with()
:
这两个函数 ,写 Kotlin 的应该很多人都用过吧?但是,也有很多人并不明白它本质上是怎么一回事。实际上,它就是像我刚才说的那样,通过函数类型的参数来强行插入了一层 this
。
不管是使用 Kotlin 现成的函数还是我们自己来实现,Kotlin 允许我们通过这种「指定」的方式来手动安插新的 this
到代码里,而不用非得用内部类才能实现。之前 Java 里嵌套的 this
,对应的全都是嵌套的类型结构;而 Kotlin 对能力这么一扩充,this
的嵌套就变得非常自由了。所以,Kotlin 引入了 implicit receiver 的概念,来方便我们对这种扩充了的场景进行描述和沟通。而本质上,所谓的 implicit receiver,指的依然是那些 this
——那些不用写的 receiver——这个本质是没有变的。
协程里的应用
Kotlin 的官方代码,以及很多第三方库,都重度地依赖这个叫做 implicit receiver 的东西。虽然我们可以说「它不就是 this
嘛」,但关键是,它给我们带来了很大的方便,怎么叫其实是次要的。随便举个例子,我们知道协程的启动是一定要用 CoroutineScope
才行的:
但是为什么在协程的内部再启动新的协程,就不用写 CoroutineScope
了?
因为它有一个隐式的 CoroutineScope
作为 this
被提供了:
怎么提供的?还是一样的方法:
总结
其他很多官方源码以及第三方库,都有类似的应用,而我们自己也可以在代码里用这样的写法去安插新的 this
层级,或者说——安插 implicit receiver,隐式的 receiver。看起来好像很复杂,但当你明白它的这些本质逻辑,写起来就很简单了。
试一下?试一下你就会发现,真的不难。
好今天内容就到这里。关注我,了解更多开发知识和技能。我是扔物线,我不和你比高低,我只助你成长。我们下期见!
版权声明
本文首发于:https://rengwuxian.com/implicit-receiver/
微信公众号:扔物线
转载时请保留此声明