它与扩展功能有什么关系?为什么是with
功能而不是关键字?
似乎没有关于该主题的明确文档,只有关于扩展的知识假设。
Answers:
确实,关于接收器概念的现有文档似乎很少(只有一小部分与扩展功能有关的注释),这令人惊讶:
所有这些主题都有文档,但是没有关于接收器的任何内容。
第一:
Kotlin中的任何代码块都可以具有(甚至多个)类型作为接收者,从而使功能和属性在该代码块中可用而无需对其进行限定。
想象一下这样的代码块:
{ toLong() }
没什么意义吧?事实上,在这个分配函数类型的(Int) -> Long
-这里Int
是(只)参数,返回类型是Long
-将理所当然地导致编译错误。您可以通过仅使用隐式单个参数限定函数调用来解决此问题it
。但是,对于DSL构建,这将导致很多问题:
html { it.body { // how to access extensions of html here? } ... }
it
调用中乱码,尤其是对于大量使用其参数(即将成为接收者)的lambda。这是接收者起作用的地方。
通过分配的代码块到具有函数类型Int
的接收器(!不作为参数),代码编译突然:
val intToLong: Int.() -> Long = { toLong() }
这里发生了什么?
本主题假定您熟悉函数类型,但需要为接收者提供一些注意事项。
通过在函数类型前面加上一个点和一个点,函数类型也可以具有一个接收器。例子:
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() }
请注意,在类内部,我们不需要使用它this
进行访问transformToBar
-同一事件在具有接收器的块中发生。
它只是恰巧,在文档这也解释了如何使用最外面的接收器,如果当前代码块有两个接收器,通过资格此。
是。一个代码块可以有多个接收者,但是当前在类型系统中没有表达式。存档的唯一方法是通过多个高阶函数采用单个接收器函数类型的。例:
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是您的朋友!
为什么所有这些都很重要?有了这些知识:
toLong()
在数字上使用扩展功能,而不必以某种方式引用该数字。也许您的扩展功能不应该是扩展?with
,标准库功能而不是关键字的原因-修改代码块以节省冗余类型的行为如此普遍,语言设计者将其正确地放在了标准库中。(Foo).() -> Unit
为一个以aFoo
作为接收器且没有参数的函数。如果是这样,那么您为什么要使用一个参数来调用它Foo()
?
Kotlin支持“带有接收器的函数文字”的概念。它可以访问可见的lambda接收者的方法和属性,而无需任何其他限定符。这与扩展功能非常相似中,还可以访问扩展内部的接收器对象的可见成员。
一个简单的示例(也是Kotlin标准库中最大的功能之一)是apply
:
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
如您所见,block
此处将带有接收器的函数文字用作参数。只需执行该块,然后返回接收者(是的实例T
)。实际上,它如下所示:
val foo: Bar = Bar().apply {
color = RED
text = "Foo"
}
我们实例化一个对象Bar
并对其进行调用apply
。的实例Bar
成为“接收者”。的block
,如在参数传递{}
(lambda表达式)不需要使用额外的限定词来访问和修改所示可见性color
和text
。
带有接收器的lambda的概念也是用Kotlin编写DSL的最重要功能。
{...}
inapply{...}
只是lambda函数作为的参数apply
。lambda是尾随lambda,不必在应用括号中。它实际上可能是apply({...}),当我第一次学习它时,这对我来说就不会那么混乱。kotlinlang.org/docs/reference/...
var greet: String.() -> Unit = { println("Hello $this") }
这定义了一个type 变量String.() -> Unit
,它告诉您
String
是接收者 () -> Unit
是函数类型像上面提到的F. George一样,可以在方法主体中调用此接收方的所有方法。
因此,在我们的示例中,this
用于打印String
。该函数可以通过以下方式调用:
greet("Fitzgerald") // result is "Hello Fitzgerald"
上面的代码段摘自Simon Wirtz的Kotlin Function Literals with Receiver – Quick Introduction。
greet
被定义为具有String
接收器但没有参数的方法。所以我知道我们可以打电话"Fitzgerald".greet()
,但是我们怎么打电话greet("Fitzgerald")
?
简而言之(没有任何额外的单词或复杂性),“接收器”是在扩展函数或类名称中扩展的类型。使用以上答案中给出的示例
fun Foo.functionInFoo(): Unit = TODO()
类型“ Foo”是“接收者”
var greet: String.() -> Unit = { println("Hello $this") }
类型“字符串”是“接收者”
附加提示:在“ fun”(函数)声明中的fullstop(。)之前查找Class
fun receiver_class.function_name() {
//...
}
通常,在Java或Kotlin中,您具有带有输入参数类型T的方法或函数。在Kotlin中,您还可以具有接收类型T值的扩展函数。
例如,如果您有一个接受String参数的函数:
fun hasWhitespace(line: String): Boolean {
for (ch in line) if (ch.isWhitespace()) return true
return false
}
将参数转换为接收器(可以使用IntelliJ自动执行):
fun String.hasWhitespace(): Boolean {
for (ch in this) if (ch.isWhitespace()) return true
return false
}
我们现在有一个扩展函数,它接收一个String,我们可以使用 this
之前的对象实例。是接收者。本质上,这就是您将在其中定义此lambda的“范围”。实际上,这就是您需要了解的所有信息,因为您将在lambda中使用的功能和属性(变量,随播广告等)将是此范围内提供的。
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
您可以使用所有参数和返回类型来定义此变量,但是在所有已定义的构造中,只有对象实例可以调用var,就像它将扩展函数并为其提供构造一样,因此“接收”它。因此,接收器将被宽松地定义为使用惯用形式的lambda为其定义扩展功能的对象。