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
对于我们前面的例子,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() }
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()
}
}
}
val aliceGreeter = Greeter("Alice")
val bobGreeter = Greeter("Bob")
aliceGreeter.displayGreeting() // prints "Hello, Alice!"
bobGreeter.displayGreeting() // prints "Hello, Bob!"
// `Int` is the receiver type
// `this` is the receiver object
fun Int.squareDouble() = toLong() * this
// a receiver object `8` of type `Int` is passed to the `square` function
val result = 8.square()
字符串 一个函数字面量的例子,这是几乎相同的:
// `Int` is the receiver type
// `this` is the receiver object
val square: Int.() -> Long = { toLong() * this }
// a receiver object `8` of type `Int` is passed to the `square` function
val result1 = 8.square()
val result2 = square(8) // this call is equal to the previous one
class Music(){
var track:String=""
fun printTrack():Unit{
println(track)
}
}
//Music class is the receiver of this function, in other words, the lambda can be piled after a Music class just like its extension function Since Music is an instance, refer to it by 'this', refer to lambda parameters by 'it', like always
val track_name:Music.(String)->Unit={track=it;printTrack()}
/*Create an Instance of Music and immediately call its function received by the name 'track_name', and exclusively available to instances of this class*/
Music().track_name("Still Breathing")
//Output
Still Breathing
8条答案
按热度按时间wztqucjr1#
确实,似乎几乎没有关于接收器概念的现有文档(只有一个与扩展函数相关的小边注),这是令人惊讶的,因为:
with
,它在没有接收器知识的情况下可能看起来像一个 * 关键字 *;所有这些主题都有文档,但没有深入的接收器。
第一个:
什么是接收器?
Kotlin中的任何代码块都可能有一个类型(甚至多个类型)作为接收器,使接收器的函数和属性在该代码块中可用,而无需对其进行限定。
想象一下这样的代码块:
字符串
这没有多大意义,对吧?事实上,将其分配给
(Int) -> Long
的函数类型-其中Int
是(唯一的)参数,返回类型是Long
-将正确地导致编译错误。您可以通过简单地使用隐式单个参数it
限定函数调用来解决这个问题。然而,对于DSL构建,这将导致一系列问题:html { it.body { // how to access extensions of html here? } ... }
个这可能不会对HTML DSL造成问题,但可能会对其他用例造成问题。
it
调用,特别是对于经常使用其参数(即将成为receiver)的Apache。这就是接收器发挥作用的地方。
通过将这段代码分配给一个以
Int
作为接收器(而不是参数!)的函数类型,代码突然编译:型
这是怎么回事?
一个小插曲
本主题假定您熟悉函数类型,但还需要对接收者做一点补充说明。
函数类型也可以有 * 一个 * 接收器,方法是在它前面加上类型和一个点。示例:
型
这些函数类型的参数列表以接收者类型为前缀。
使用接收器解析代码
实际上,理解如何处理带有接收器的代码块是非常容易的:
想象一下,类似于扩展函数,代码块在接收者类型的类中进行评估。this实际上被接收者类型修改。
对于我们前面的例子,
val intToLong: Int.() -> Long = { toLong() }
,它有效地导致代码块在不同的上下文中被评估,就好像它被放置在Int
内部的函数中一样。下面是一个使用手工类型的不同例子,它更好地展示了这一点:型
有效地变成了(在头脑中,不是代码明智的-你实际上不能在JVM上扩展类):
型
请注意,在类内部,我们不需要使用
this
来访问transformToBar
--同样的事情发生在有接收器的块中。碰巧的是,this的文档还解释了如果当前代码块有两个接收器,如何使用最外面的接收器,通过一个限定的this。
等等,多个接收器?
是的。一个代码块可以有 * 多个 * 接收器,但这在类型系统中目前没有表达式。实现这一点的唯一方法是通过多个higher-order functions,它们采用单个接收器函数类型。示例:
型
请注意,如果Kotlin语言的这个特性似乎不适合您的DSL,@DslMarker是您的朋友!
结论
为什么这一切都很重要?有了这些知识:
toLong()
,而不必以某种方式引用这个数字。Maybe your extension function shouldn't be an extension?with
,一个标准库函数,而不是一个关键字,存在-修改代码块的范围以保存冗余类型的行为是如此常见,语言设计者把它放在标准库中。cl25kdpy2#
当您致电:
字符串
字符串
"Hello, World!"
的长度叫做receiver。更一般地说,任何时候你写
someObject.someFunction()
,在对象和函数名之间有一个.
,对象就充当了函数的接收器。这并不是Kotlin特有的,在许多使用对象的编程语言中都很常见。所以接收器的概念对你来说可能非常熟悉,即使你以前没有听说过这个术语。它被称为接收器,因为你可以把函数调用看作是发送一个对象将接收的请求。
不是所有函数都有接收器。例如,Kotlin的
println()
函数是顶级函数。当你写:型
你不需要在函数调用之前放置任何对象(或
.
)。没有接收器,因为println()
函数不存在于对象中。接收端
现在让我们从接收者的Angular 来看看函数调用是什么样子的。假设我们写了一个类,它显示一个简单的问候消息:
型
要调用
displayGreeting()
,我们首先创建Greeter
的示例,然后我们可以使用该对象作为接收器来调用函数:型
displayGreeting
函数如何知道每次显示哪个名称?答案是关键字this
,它总是指当前接收者。aliceGreeter.displayGreeting()
时,接收者是aliceGreeter
,所以this.name
指向"Alice"
。bobGreeter.displayGreeting()
时,接收者是bobGreeter
,所以this.name
指向"Bob"
。隐式接收器
大多数情况下,实际上不需要写
this
。我们可以用name
替换this.name
,它将隐式地指向当前接收器的name
属性。型
请注意这与从类外部访问属性的不同之处。要从外部打印名称,我们必须写出接收者的全名:
型
通过在类内部编写函数,我们可以完全省略接收器,使整个过程变得更短。对
name
的调用仍然有一个接收器,我们只是不需要将它写出来。我们可以说我们使用隐式接收器访问name
属性。一个类的成员函数经常需要访问自己类的许多其他函数和属性,因此隐式接收器非常有用。它们缩短了代码,并且可以使其更容易阅读和编写。
接收者如何与扩展关联?
到目前为止,接收器似乎为我们做了两件事:
1.发送一个函数调用到一个特定的对象,因为该函数存在于该对象中
1.允许函数方便而简洁地访问同一对象中的其他属性和函数
如果我们想写一个函数,它可以使用隐式接收器来方便地访问对象的属性和函数,但我们不想(或不能)在那个对象/类中写我们的新函数,那该怎么办?这就是Kotlin的扩展函数的用武之地。
型
这个函数不在
Greeter
内部,但是它访问Greeter
* 就像它是一个接收器 *。注意函数名之前的接收器类型,这告诉我们这是一个扩展函数。在扩展函数的主体中,我们可以再次访问没有接收器的name
,即使我们实际上不在Greeter
类中。你可以说这不是一个“真实的”接收器,因为我们实际上并没有将函数调用发送到对象。函数位于对象之外。我们只是使用接收器的语法和外观,因为它使代码变得方便和简洁。我们可以称之为扩展接收器,将其与真正存在于对象内部的函数的分派接收器区分开来。
扩展函数的调用方式与成员函数相同,在函数名之前有一个接收器对象。
型
因为函数总是在函数名之前的接收者位置调用对象,所以它可以使用关键字
this
访问该对象。像成员函数一样,扩展函数也可以省略this
,并使用当前接收者示例作为隐式接收者来访问接收者的其他属性和函数。扩展函数有用的主要原因之一是当前扩展接收器示例可以用作函数体内的隐式接收器。
with
做什么?到目前为止,我们已经看到了两种方法来使某些东西作为隐式接收器可用:
1.在接收器类中创建一个函数
1.在类外部创建扩展函数
这两种方法都需要创建一个函数,那么我们是否可以在不声明新函数的情况下使用隐式接收器呢?
答案是调用
with
:型
在调用
with(aliceGreeter) { ... }
的块体中,aliceGreeter
可以作为隐式接收器使用,我们可以再次访问name
而不使用它的接收器。那么,
with
为什么可以作为一个函数而不是一个语言特性来实现呢?如何简单地将一个对象变成一个隐式的接收器呢?答案在于lambda函数。让我们再次考虑我们的
displayAnotherGreeting
扩展函数。我们将其声明为函数,但我们可以将其写成lambda:型
我们仍然可以像以前一样调用
aliceGreeter.displayAnotherGreeting()
,函数内部的代码也是一样的,只包含了隐式的receiver。我们的扩展函数变成了一个带有receiver的lambda。注意Greeter.() -> Unit
函数类型的编写方式,扩展接收器Greeter
列在(空的)参数列表()
之前。现在,看看当我们将这个lambda函数作为参数传递给另一个函数时会发生什么:
型
第一个参数是我们要用作接收器的对象。第二个参数是我们要运行的lambda函数。
runLambda
所做的就是调用提供的lambda参数,使用greeter
参数作为lambda的接收器。将
displayAnotherGreeting
lambda函数的代码替换为第二个参数,我们可以像这样调用runLambda
:型
就像这样,我们把
aliceGreeter
变成了一个隐式接收器。Kotlin的with
函数只是一个通用版本,可以处理任何类型。回顾
someObject.someFunction()
时,someObject
充当接收函数调用的接收者someFunction
内部,someObject
作为当前接收器示例“在范围内”,可以作为this
访问this
,并使用隐式接收器访问其属性和函数with
函数使用了一个带receiver的lambda来使接收器在任何地方都可用,而不仅仅是在成员函数和扩展函数中bpsygsoo3#
Kotlin知道带有receiver* 的 * 函数文字的概念。它允许在lambda的主体内访问lambda的receiver的可见方法和属性,而不必使用任何额外的限定符。这与扩展函数非常相似,在扩展函数中,您也可以访问扩展内receiver对象的成员。
一个简单的例子,也是Kotlin标准库中最好的函数之一,是
apply
:字符串
这里,
block
是一个带有receiver的函数文字。此块参数由函数执行,apply的receiverT
返回给调用方。在操作中,它如下所示:型
我们示例化一个
Bar
的对象,并在其上调用apply
。Bar
的示例成为apply
的接收者。作为大括号中的参数传递的block
不需要使用额外的限定符来访问和修改属性color
和text
。lambdas与接收器的概念也是使用Kotlin编写DSL的最重要的特性。
eni9jsuy4#
字符串
这定义了一个 type
String.() -> Unit
的变量,它告诉你*
String
为接收方() -> Unit
是函数类型像上面提到的F. George一样,这个接收器的所有方法都可以在方法体中调用。
因此,在我们的示例中,
this
用于打印String
。可以通过写入.调用该函数。型
上面代码片段摘自Simon维尔茨的Kotlin Function Literals with Receiver – Quick Introduction。
yquaqz185#
简单地说(没有任何额外的单词或复杂性),“Receiver”是在扩展函数或类名中扩展的类型。
字符串
类型“Foo”是“接收器”
型
类型“String”是“接收器”
额外提示:注意“fun”(函数)声明中句号(.)之前的Class
型
rn0zuynd6#
简单地说:
*接收器类型是扩展函数扩展的类型
*receiver object是扩展函数调用的对象;函数体中的
this
关键字对应于receiver对象扩展函数示例:
字符串
一个函数字面量的例子,这是几乎相同的:
型
iaqfqrcu7#
.之前的对象示例是接收者。这本质上是你将在其中定义这个lambda的“作用域”。这是你需要知道的全部,真的,因为你将在lambda中使用的函数和属性(变量,同伴等)将是在这个作用域中提供的。
字符串
你定义了这个变量,以及它将拥有的所有参数和返回类型,但是在所有定义的结构中,只有对象示例可以调用var,就像它将扩展函数提供给它一样,因此“接收”它。因此,接收者将被松散地定义为一个对象,它的扩展函数是使用Rolldas的惯用风格定义的。
a1o7rhls8#
通常在Java或Kotlin中,你有方法或函数的输入参数类型为T。在Kotlin中,你也可以有扩展函数接收T类型的值。
如果你有一个接受String参数的函数,例如:
字符串
将参数转换为接收器(您可以使用IntelliJ自动完成):
型
我们现在有一个扩展函数,它接收一个String,我们可以用
this
访问这个值