标准委托

在Kotlin的标准库中有一系列的标准委托。它们包括了大部分有用的委托,但是我们也可以创建我们自己的委托。

Lazy

它包含一个lambda,当第一次执行getValue的时候这个lambda会被调用,所以这个属性可以被延迟初始化。之后的调用都只会返回同一个值。这是非常有趣的特性, 当我们在它们第一次真正调用之前不是必须需要它们的时候。我们可以节省内存,在这些属性真正需要前不进行初始化。

class App : Application() {
    val database: SQLiteOpenHelper by lazy {
        MyDatabaseHelper(applicationContext)
    }

    override fun onCreate() {
        super.onCreate()
        val db = database.writableDatabase
    }
}

在这个例子中,database并没有被真正初始化,直到第一次调用onCreate时。在那之后,我们才确保applicationContext存在,并且已经准备好可以被使用了。lazy操作符是线程安全的。

如果你不担心多线程问题或者想提高更多的性能,你也可以使用lazy(LazyThreadSafeMode.NONE){ ... }

Observable

这个委托会帮我们监测我们希望观察的属性的变化。当被观察属性的set方法被调用的时候,它就会自动执行我们指定的lambda表达式。所以一旦该属性被赋了新的值,我们就会接收到被委托的属性、旧值和新值。

class ViewModel(val db: MyDatabase) {
    var myProperty by Delegates.observable("") {
        d, old, new ->
        db.saveChanges(this, new)
    }
}

这个例子展示了,一些我们需要关心的ViewMode,每次值被修改了,就会保存它们到数据库。

Vetoable

这是一个特殊的observable,它让你决定是否这个值需要被保存。它可以被用于在真正保存之前进行一些条件判断。

var positiveNumber = Delegates.vetoable(0) {
    d, old, new ->
    new >= 0
}

上面这个委托只允许在新的值是正数的时候执行保存。在lambda中,最后一行表示返回值。你不需要使用return关键字(实质上不能被编译)。

Not Null

有时候我们需要在某些地方初始化这个属性,但是我们不能在构造函数中确定,或者我们不能在构造函数中做任何事情。第二种情况在Android中很常见:在Activity、fragment、service、receivers……无论如何,一个非抽象的属性在构造函数执行完之前需要被赋值。为了给这些属性赋值,我们无法让它一直等待到我们希望给它赋值的时候。我们至少有两种选择方案。

第一种就是使用可null类型并且赋值为null,直到我们有了真正想赋的值。但是我们就需要在每个地方不管是否是null都要去检查。如果我们确定这个属性在任何我们使用的时候都不会是null,这可能会使得我们要编写一些必要的代码了。

第二种选择是使用notNull委托。它会含有一个可null的变量并会在我们设置这个属性的时候分配一个真实的值。如果这个值在被获取之前没有被分配,它就会抛出一个异常。

这个在单例App这个例子中很有用:

class App : Application() {
    companion object {
        var instance: App by Delegates.notNull()
    }

    override fun onCreate() {
        super.onCreate()
        instance = this
    }
}

从Map中映射值

另外一种属性委托方式就是,属性的值会从一个map中获取value,属性的名字对应这个map中的key。这个委托可以让我们做一些很强大的事情,因为我们可以很简单地从一个动态地map中创建一个对象实例。如果我们import kotlin.properties.getValue,我们可以从构造函数映射到val属性来得到一个不可修改的map。如果我们想去修改map和属性,我们也可以import kotlin.properties.setValue。类需要一个MutableMap作为构造函数的参数。

想象我们从一个Json中加载了一个配置类,然后分配它们的key和value到一个map中。我们可以仅仅通过传入一个map的构造函数来创建一个实例:

import kotlin.properties.getValue

class Configuration(map: Map<String, Any?>) {
    val width: Int by map
    val height: Int by map
    val dp: Int by map
    val deviceName: String by map
}

作为一个参考,这里我展示下对于这个类怎么去创建一个必须要的map:

conf = Configuration(mapOf(
    "width" to 1080,
    "height" to 720,
    "dp" to 240,
    "deviceName" to "mydevice"
))