kotlin - Kotlin 中的 "receiver"是什么?

它与扩展功能有什么关系?为什么 with a function 不是关键字?

该主题似乎没有明确的文档,只有引用 extensions 的知识假设。

最佳答案

确实,关于接收器概念的现有文档似乎很少(只有 small side note related to extension functions ),这令人惊讶:

  • 它们的存在源于 extension functions ;
  • 它们的作用是在 building a DSL 中使用表示的扩展函数;
  • 标准库的存在 函数 with ,如果不知道接收者,它可能看起来像一个关键字;
  • 完全是 separate syntax for function types 。

  • 所有这些主题都有文档,但没有深入介绍接收器。

    第一的:
    什么是接收器?
    Kotlin 中的任何代码块都可能有一个类型(甚至多种类型)作为 接收器 ,使​​接收器的功能和属性在该代码块中可用,而无需对其进行限定。
    想象一下这样的代码块:
    { toLong() }
    
    没有多大意义,对吧?事实上,将它分配给 (Int) -> Long 的 function type - 其中 Int 是(唯一的)参数,并且返回类型是 Long - 将正确地导致编译错误。您可以通过简单地使用隐式单个参数 it 限定函数调用来解决此问题。但是,对于 DSL 构建,这会导致一系列问题:
  • DSL 的嵌套块的上层将被遮蔽:html { it.body { // how to access extensions of html here? } ... }这可能不会导致 HTML DSL 出现问题,但可能会导致其他用例出现问题。
  • 它可以用 it 调用乱扔代码,特别是对于大量使用其参数(即将成为接收器)的 lambda。

  • 这就是 接收器 发挥作用的地方。
    通过将此代码块分配给具有 Int 作为 接收器 (不是作为参数!)的函数类型,代码突然编译:
    val intToLong: Int.() -> Long = { toLong() }
    
    这里发生了什么?

    一个小小的旁注
    本主题假设您熟悉 function 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
    
    此类函数类型的参数列表以接收器类型为前缀。

    使用接收器解析代码
    理解如何处理带有接收器的代码块实际上非常容易:
    想象一下,与扩展函数类似,代码块在接收器类型的类中进行评估。 this 有效地被接收器类型修改。
    对于我们之前的示例 val intToLong: Int.() -> Long = { toLong() } ,它有效地导致代码块在不同的上下文中进行评估,就好像它被放置在 Int 中的函数中一样。这是一个使用手工制作类型的不同示例,可以更好地展示这一点:
    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 - 同样的事情发生在具有接收器的块中。
    碰巧的是,关于 this 的文档还通过 qualified this 解释了如果当前代码块有两个接收器时如何使用最外层接收器。

    等等,多个接收器?
    是的。一个代码块可以有多个接收器,但目前在类型系统中没有表达式。实现这一点的唯一方法是通过多个 higher-order functions 采用单一接收器函数类型。例子:
    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 语言的此功能似乎不适合您的 DSL,那么 @DslMarker 就是您的 friend !

    结论
    为什么这一切都很重要?有了这些知识:
  • 您现在明白为什么可以在数字的扩展函数中写入 toLong(),而不必以某种方式引用该数字。 Maybe your extension function shouldn't be an extension?
  • 你可以为你最喜欢的标记语言构建一个 DSL,也许有助于解析一种或另一种( who needs regular expressions? !)。
  • 你明白为什么 with ,一个标准库 函数 而不是关键字,存在 - 修改代码块的范围以节省冗余类型的行为如此普遍,语言设计者把它放在标准库中。
  • (也许)你在分支上学到了一些关于函数类型的知识。
  • 关于kotlin - Kotlin 中的 "receiver"是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45875491/

    相关文章:

    unit-testing - 在 Kotlin 中测试预期的异常

    lambda - 传递 lambda 而不是接口(interface)

    kotlin - 什么时候应该更喜欢 Kotlin 扩展函数?

    android - Kotlin: "return@"是什么意思?

    java - Android 动画 Alpha

    kotlin - 如何在 Kotlin 中同时捕获多个异常?

    java - Kotlin 'when' 语句与 Java 'switch'

    android - 找不到字段的 setter - 将 Kotlin 与 Room 数据库结合使用

    kotlin - Kotlin 中线程和协程的区别

    spring - 如何在基于 Spring 的强类型语言中正确执行 PATCH - 示例