什么是Kotlin的“接收器”?
这与扩展function有什么关系? 为什么with
一个函数而不是一个关键字?
似乎没有这个主题的明确文件,只是关于扩展的知识假设。
确实,接收者的概念似乎很少有文件(只有与扩展函数有关的小副本 ),这是令人惊讶的:
- 它们的存在是从扩展function出发的;
- 他们在使用所述扩展function来构建DSL中的角色;
- 标准库函数的存在,在不知道接收者的情况下可能看起来像一个关键字 ;
- 一个完全独立的函数types的语法 。
所有这些话题都有文档,但没有深入的接收器。
第一:
什么是接收器?
Kotlin中的任何代码块都可能具有一个(甚至多个)types的接收器 ,使得该代码块中的接收器的function和属性无需validation。
想象一下这样的代码块:
{ toLong() }
没有多大意义,对吗? 实际上,将其分配给函数types为(Int) -> Long
(其中, Int
是(唯一)参数,并且返回types为Long
)会导致编译错误。 你可以通过简单地用隐式单参数来限定函数调用来解决这个it
。 但是,对于DSL构建而言,这会造成一系列的问题:
- DSL的嵌套块将会有上层的阴影:
html { it.body { // how to access extensions of html here? } ... }
这可能不会导致HTML DSL的问题,但可能会用于其他用例。 - 它可以乱抛垃圾代码,特别是使用它们的参数的lambda(很快就成为接收者)。
这就是接收器的作用。
通过将这段代码分配给一个具有Int
作为接收者的函数types(而不是一个参数!),代码突然编译:
val intToLong: Int.() -> Long = { toLong() }
这里发生了什么?
有一点小贴士
这个主题假设与函数types相似,但是需要接收者的一点注意。
函数types也可以有一个接收器,前缀为types和点。 例子:
Int.() -> Long // taking an integer as receiver producing a long String.(Long) -> String // taking a string as receiver and long as parameter producing a string GUI.() -> Unit // taking an GUI and producing nothing
这些函数types的参数列表以接收器types为前缀。
用接收器解析代码
实际上,理解如何处理接收器的代码块是非常容易的:
想象一下,类似于扩展函数,代码块在接收器types的类中被评估。 这有效地被接收机types修改。
对于我们前面的例子, val intToLong: Int.() -> Long = { toLong() }
,它有效地导致代码块在不同的上下文中进行评估,就好像它放在Int
中的函数中一样。 下面是使用手工types的不同示例,可以更好地展示这一点:
class Bar class Foo { fun transformToBar(): Bar = TODO() } val myBlockOfCodeWithReceiverFoo: (Foo).() -> Bar = { transformToBar() }
有效地成为(在头脑中,不是代码明智的 – 你实际上不能在JVM上扩展类):
class Bar class Foo { fun transformToBar(): Bar = TODO() fun myBlockOfCode(): Bar { return transformToBar() } } val myBlockOfCodeWithReceiverFoo: (Foo) -> Bar = { it.myBlockOfCode() }
注意一个类的内部,我们不需要使用this
来访问transformToBar
– 同一个事情发生在一个接收方的块中。
恰恰相反,关于这个的文档也解释了如果当前的代码块有两个接收者,通过合格的这个 ,如何使用最外层的接收者。
等等,多个接收器?
是。 一个代码块可以有多个接收器,但是目前在types系统中没有expression式。 实现这一点的唯一方法是通过采用单个接收器函数types的多个高阶函数 。 例:
class Foo class Bar fun Foo.functionInFoo(): Unit = TODO() fun Bar.functionInBar(): Unit = TODO() inline fun higherOrderFunctionTakingFoo(body: (Foo).() -> Unit) = body(Foo()) inline fun higherOrderFunctionTakingBar(body: (Bar).() -> Unit) = body(Bar()) fun example() { higherOrderFunctionTakingFoo { higherOrderFunctionTakingBar { functionInFoo() functionInBar() } } }
请注意,如果Kotlin语言的这个function似乎不适合您的DSL, @DslMarker是您的朋友!
结论
为什么所有这些事情? 因为:
有了这些知识:
- 你现在明白了为什么你可以在一个数字的扩展函数中写入
toLong()
,而不必以某种方式引用该数字。 也许你的扩展function不应该是一个扩展? - 你可以为你喜欢的标记语言建立一个DSL,也许可以帮助解析这个或那个 ( 谁需要正则expression式? !)。
- 您明白为什么
with
标准库函数而不是关键字存在 – 修改代码块的范围以节省redudanttypes的行为非常普遍,语言设计人员将其放在标准库中。 - (也许)你在分支上学到了一些关于函数types的知识。
F.乔治的解释是一个很好的解释。
我学习的方式是阅读几篇不同的教程和文章,因为每篇都以不同的方式解释了一些东西。 对于那些正在寻找其他资源的人
var greet: String.() -> Unit = { println("Hello $this") }
这个定义了一个types为 String.() -> Unit
的variables,告诉你
-
String
是接收器 -
() -> Unit
是函数types
像F.乔治在上面提到的那样, 这个接收器的所有方法都可以在方法体中调用 。
所以,在我们的例子中, this
是用来打印String
。 该函数可以通过编写调用…
greet("Fitzgerald") // result is "Hello Fitzgerald"
上面的代码片段是由Simon Wirtz 撰写的KOTLIN FUNCTION LITERALS WITH RECEIVER – 快速介绍 。
函数Literals / Lambda与接收器
Kotlin支持“function文字与接收器”的概念。 它允许访问lambda的接收者的可见方法和属性, 而不需要任何特定的限定符 。 这与扩展函数非常相似,在扩展函数中也可以访问扩展中的接收者对象的可见成员。
一个简单的例子,也是Kotlin标准库中最重要的function之一, apply
:
public inline fun T.apply(block: T.() -> Unit): T { block(); return this }
正如你所看到的,这样一个带有接收者的函数文字就是这里的参数block
。 这个块被简单地执行,接收者(它是T
一个实例)被返回。 在行动中,这看起来如下:
val foo: Bar = Bar().apply { color = RED text = "Foo" }
我们实例化一个Bar
的对象,然后调用它。 Bar
的实例成为“接收者”。 作为{}
(lambdaexpression式)中的parameter passing的block
不需要使用其他限定符来访问和修改显示的可见属性的color
和text
。
带有接收器的lambdas的概念也是用Kotlin编写DSL的最重要的特性。