Kotlin “a”是什么;“接收者”;在科特林?
它与扩展功能有什么关系?为什么Kotlin “a”是什么;“接收者”;在科特林?,kotlin,Kotlin,它与扩展功能有什么关系?为什么带有,而不是关键字 关于这个主题似乎没有明确的文档,只有关于知识的假设。的确,关于接收者概念的现有文档似乎很少(只有a),这是令人惊讶的: 他们的存在源于 它们在使用所述扩展功能中的作用 标准库函数的存在,在不了解接收者的情况下,可能看起来像一个关键词 a完全正确 所有这些主题都有文档,但没有深入介绍接收器 第一: 什么是接收器? Kotlin中的任何代码块都可能有一个(甚至多个)类型作为接收器,使接收器的功能和属性在该代码块中可用,而无需对其进行限定 想象一
带有,而不是关键字
关于这个主题似乎没有明确的文档,只有关于知识的假设。的确,关于接收者概念的现有文档似乎很少(只有a),这是令人惊讶的:
- 他们的存在源于李>
- 它们在使用所述扩展功能中的作用李>
- 标准库函数的存在,在不了解接收者的情况下,可能看起来像一个关键词李>
- a完全正确
所有这些主题都有文档,但没有深入介绍接收器
第一:
什么是接收器?
Kotlin中的任何代码块都可能有一个(甚至多个)类型作为接收器,使接收器的功能和属性在该代码块中可用,而无需对其进行限定
想象一下这样的代码块:
{ toLong() }
没什么意义,对吧?事实上,将其分配给of(Int)->Long
——其中Int
是(唯一)参数,返回类型是Long
——将正确地导致编译错误。您可以通过使用隐式单参数限定函数调用来解决此问题。但是,对于DSL构建,这将导致一系列问题:
- DSL的嵌套块将对其上层进行阴影处理:
html{it.body{//如何在此处访问html扩展?}…}
这可能不会导致HTML DSL出现问题,但可能会导致其他用例出现问题
- 它可以在
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
此类函数类型的参数列表前缀为receiver类型
使用接收器解析代码
实际上,理解如何处理带有接收器的代码块非常容易:
想象一下,与扩展函数类似,代码块是在receiver类型的类中计算的。有效地被接收器类型修改。
对于我们前面的示例,val intToLong:Int.()->Long={toLong()}
,它有效地导致代码块在不同的上下文中进行计算,就像它被放在Int
中的函数中一样。下面是一个使用手工制作类型的不同示例,更好地展示了这一点:
class Bar
class Foo {
fun transformToBar(): Bar = TODO()
}
val myBlockOfCodeWithReceiverFoo: (Foo).() -> Bar = { transformToBar() }
实际上(在思想上,不是代码方面——您实际上无法在JVM上扩展类):
请注意,在类内部,我们不需要使用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,您的朋友就是您的朋友
结论
为什么这一切都很重要?有了这些知识:
- 现在您了解了为什么可以在扩展函数中对数字编写
toLong()
,而不必以某种方式引用数字李>
- 您可以为您最喜欢的标记语言构建DSL,可能有助于解析其中一种(!)
- 你知道为什么存在一个标准库函数而不是一个关键字-修改代码块的范围以节省冗余键入的行为是如此普遍,语言设计者将其放在标准库中
- (可能)您在分支上了解了一些函数类型
这定义了一个String.(->Unit
类型的变量,它告诉您
字符串
是接收者
()->单元
是函数类型
如上所述,可以在方法体中调用此接收器的所有方法
因此,在我们的示例中,这个
用于打印字符串
。该函数可以通过写入
greet("Fitzgerald") // result is "Hello Fitzgerald"
上面的代码片段来自Simon Wirtz
Kotlin支持“具有接收者的函数文本”的概念。它允许访问主体中lambda的接收器的可见方法和属性,而无需任何附加限定符。这与扩展功能非常类似,在扩展功能中,还可以访问扩展中接收器对象的可见成员
一个简单的例子,也是Kotlin标准库中最大的函数之一,是apply
:
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
我们实例化一个Bar
对象,并对其调用apply
。Bar
的实例成为“接收者”。在{}
(lambda表达式)中作为参数传递的块
,不需要使用其他限定符来访问和修改显示的可见属性颜色
和文本
带接收器的lambdas概念也是使用Kotlin编写DSL的最重要功能。简单地说(没有任何e
greet("Fitzgerald") // result is "Hello Fitzgerald"
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
val foo: Bar = Bar().apply {
color = RED
text = "Foo"
}
fun Foo.functionInFoo(): Unit = TODO()
var greet: String.() -> Unit = { println("Hello $this") }
fun receiver_class.function_name() {
//...
}
fun hasWhitespace(line: String): Boolean {
for (ch in line) if (ch.isWhitespace()) return true
return false
}
fun String.hasWhitespace(): Boolean {
for (ch in this) if (ch.isWhitespace()) return true
return false
}
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