什么是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不需要使用其他限定符来访问和修改显示的可见属性的colortext

带有接收器的lambdas的概念也是用Kotlin编写DSL的最重要的特性。