在Kotlin中,'by'关键字的作用是什么?

262
在开发Android应用时,有时会遇到类似这样的情况:
var someModel: someViewModel by notNullAndObservable { vm ->
  ...
}

我不明白by关键字的意义是什么。

14
https://kotlinlang.org/docs/reference/delegated-properties.html - JB Nizet
4个回答

436

简单来说,你可以理解关键字by由提供

从属性使用者的角度来看,val表示具有getter(get)的内容,var表示具有getter和setter(get、set)的内容。对于每个var属性,都有默认的getter和setter方法提供程序,我们不需要显式指定。

但是,当使用by关键字时,您在说明此getter/getter&setter已在其他地方提供(即已被委派)。它是由by后面的函数提供的

因此,您代理了某些明确的功能来完成这个内置的获取和设置方法的工作。

一个非常常见的示例是用于延迟加载属性的by lazy。此外,如果您正在使用像Koin这样的依赖注入库,您会看到许多像这样定义的属性:

var myRepository: MyRepository by inject()  //inject is a function from Koin
在类的定义中,它遵循同样的原则,它定义了一些函数所提供的位置,但它可以引用任何一组方法/属性,而不仅仅是get和set。
class MyClass: SomeInterface by SomeImplementation, SomeOtherInterface

这段代码的意思是: “我是MyClass类,提供了SomeInterface接口的函数,它们由SomeImplementation提供。我会自己实现SomeOtherInterface接口(那是隐式的,所以没有by关键字)。”


38
你的解释简明扼要。 - user1154390
7
对我来说,这比被接受的答案更易于理解。 - Vaisakh N
1
这个解释非常好。我真的很喜欢你将代码解释为可读的句子的方式,它让我真正理解了代码背后发生的事情。谢谢你的回答。 - Ahmad Hamwi
MyClass 的最后一个例子可以理解,但像 var myRepository: MyRepository by inject() 这样的语句对于像我这样的初学者来说毫无意义。 - Bhavesh Achhada

130
Kotlin参考资料中,您将找到by的两种用途,第一种是委托属性,这是您上面所使用的用法:

有某些常见类型的属性,尽管我们每次需要它们时都可以手动实现它们,但一劳永逸地实现它们并放入库中会非常好。例如延迟初始化属性:
只有在首次访问时才计算值
可观察属性:
监听器会收到此属性更改的通知
将属性存储在映射中,而不是在单独的字段中等等。

在这里,您将getter / setter委托给另一个执行工作的类,并且可以包含通用代码。例如,Kotlin的某些依赖注入器支持通过委托getter从由依赖注入引擎管理的实例注册表中接收值来实现此模型。

接口/类委托是其他用途:

委托模式已被证明是实现继承的一个好替代方案,而Kotlin在本地支持此模式,不需要零样板代码。类Derived可以从接口Base继承,并将其所有公共方法委托给指定对象。

在这里,您可以将接口委托给另一个实现,因此实现类只需要覆盖它想要更改的内容,而其余方法则委托回更完整的实现。

一个活生生的例子是Klutter Readonly/Immutable collections,在那里它们实际上只是将特定的集合接口委托给另一个类,然后在readonly实现中覆盖任何需要不同的内容。这样就不必手动委托所有其他方法,从而节省了很多工作。

这两者都包含在Kotlin语言参考文档中。从那里开始了解该语言的基础主题。


90

语法如下:

enter image description here

val/var <property name>: <Type> by <expression>. 

在 by 关键字后面的表达式是委托。

如果我们尝试访问属性p的值,换句话说,如果我们调用属性pget()方法,则会调用Delegate实例的getValue()方法。

如果我们尝试设置属性p的值,换句话说,如果我们调用属性pset()方法,则会调用Delegate实例的setValue()方法。


执行 e.p = "NEW" 后,如果我们执行 println(e.p),它会打印出新值 NEW 吗?实际结果是它与原始值相同。为什么会这样? - LiuWenbin_NO.
2
@oiyio 的图表和解释非常有用。谢谢。 - Luc-Olivier
这个答案是以图像形式呈现的,对我来说更容易理解。所以,你是在说“by”是明确的懒惰吗?变量/常量在被调用之前不会被计算。 - undefined

55

班级的委派:

interface BaseInterface {
    val value: String
    fun f()
}

class ClassA: BaseInterface {
    override val value = "property from ClassA"
    override fun f() { println("fun from ClassA") }
}

// ClassB implements BaseInterface using all public members of classA.
class ClassB: BaseInterface by ClassA()

object SampleBy {
    @JvmStatic fun main(args: Array<String>) {
        val classB = ClassB()
        println(classB.value)
        classB.f()
    }
}

结果:

property from ClassA
fun from ClassA

财产委托:

import kotlin.reflect.KProperty

class Delegate {
    // for get() method, ref - a reference to the object from 
    // which property is read. prop - property
    operator fun getValue(ref: Any?, prop: KProperty<*>) = "textA"
    // for set() method, 'v' stores the assigned value
    operator fun setValue(ref: Any?, prop: KProperty<*>, v: String) {
        println("value = $v")
    }
}

object SampleBy {
    var s: String by Delegate() // delegation for property
    @JvmStatic fun main(args: Array<String>) {
        println(s)
        s = "textB"
    }
}

结果:

textA
value = textB

参数委派:

// for val properties Map is used; for var MutableMap is used
class User(mapA: Map<String, Any?>, mapB: MutableMap<String, Any?>) {
    val name: String by mapA
    val age: Int by mapA
    var address: String by mapB
    var id: Long by mapB
}

object SampleBy {
    @JvmStatic fun main(args: Array<String>) {
        val user = User(mapOf("name" to "John", "age" to 30),
            mutableMapOf("address" to "city, street", "id" to 5000L))

        println("name: ${user.name}; age: ${user.age}; " +
            "address: ${user.address}; id: ${user.id}")
    }
}

结果:

name: John; age: 30; address: city, street; id: 5000

2
完美的答案... - X-Black...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接