轻松掌握Kotlin委托机制

属性的委托

属性的委托指的是一个类中的某个属性的值不是在类中直接进行定义,而是由某个类的方法来进行 setter 和 getter。默认属性委托都是线程安全的。属性委托适合那些属性的需要复杂的计算但是计算过程可以被重用的场合。

使用委托的语法是: val/var <属性名>: <类型> by <表达式>。在by后面的表达式是该属性的委托, 因为属性对应的 get()(和 set())会被委托给它的 getValue() 和 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 函数(和 setValue()——对于 var 属性)。例如:

class Delegate {

    private var message = "Default Message"

    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
        return "${prop.name} = $message from $thisRef"
    }

    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
        message = value
    }
} 

定义一个包含属性委托的类,然后访问该属性

class Example { 
    var msg: String by Delegate() 
}

val e = Example()
println(e.msg)  //  msg = Default Message
e.msg = "New Message"
println(e.msg)  //  msg = New Message

在使用属性委托时,被委托的类的方法(即接收者)的返回值必须与委托的属性相同。

标准委托

Kotlin 标准库为几种有用的委托提供了工厂方法。

延迟属性 Lazy

lazy() 是接受一个 lambda 并返回一个 Lazy 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

//输出结果:
//computed!
//Hello
//Hello

默认情况下,对于 lazy 属性的求值是同步锁的(synchronized):该值只在一个线程中计算,并且所有线程会看到相同的值。如果初始化委托的同步锁不是必需的,这样多个线程可以同时执行,那么将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么你可以使用 LazyThreadSafetyMode.NONE 模式, 它不会有任何线程安全的保证和相关的开销。

可观察属性 Observable

Delegates.observable() 接受两个参数:初始值和修改时处理程序(handler)。 每当我们给属性赋值时会调用该处理程序(在赋值后执行)。它有三个参数:被赋值的属性、旧值和新值:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}

//输出结果
// -> first
//first -> second

如果你想要截获一个赋值并“否决”它,就使用 vetoable() 取代 observable()。 在属性被赋新值生效之前会调用传递给 vetoable 的处理程序。

NotNull

notNull 适用于那些无法在初始化阶段就确定属性值的场合。在初始化前读取该属性会导致异常。

class Foo {
    var notNullBar: String by Delegates.notNull<String>()
}

foo.notNullBar = "bar"
println(foo.notNullBar)

以 Map 形式保存属性的值

Kotlin 中有一种特别的委托,可以以 Map 作为一个类的构造方法的参数,访问该类的属性就是访问该 Map 的键值对。这种做法非常类似 Groovy 中的带名构造方法。

要实现这一功能需要得意于 Kotlin 内置的属性的扩展方法 kotlin.properties.getValue

import kotlin.properties.getValue

class Person(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int by map
}

可以像普通类一样访问其各个属性:

val person = Person(mapOf(
    "name" to "John",
    "age" to 25
))
println(person.name) // Prints "John Doe"
println(person.age) // Prints 25

对于可变值,可以使用 MutableMap 。

局部委托属性(自 1.1 起)

你可以将局部变量声明为委托属性。 例如,你可以使一个局部变量惰性初始化:

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。

实现自己的委托类

自定义的委托类可以实现包含所需 operator 方法的 ReadOnlyProperty 或 ReadWriteProperty 接口之一。 这俩接口是在 Kotlin 标准库中声明的:

interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}

interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}

Android中的Preference是将数据存储到本地的一种方式,通过Kotlin的这种委托机制,使得存储数据到本地和从本地存储读取数据更加轻松。

class PreferenceHelper(val context:Context, val name:String, val default: T): ReadWriteProperty {
    val prefs by lazy {
        context.getSharedPreferences("default", Context.MODE_PRIVATE)
    }

    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
//        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        return findPreference(name, default)
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
//        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
        putPreference(name, value)
    }

    private fun  findPreference(name:String, default: U):U = with(prefs)
    {
        val res: Any = when(default)
        {
            is Long -> getLong(name, default)
            is String -> getString(name, default)
            is Int -> getInt(name, default)
            is Boolean -> getBoolean(name, default)
            is Float -> getFloat(name, default)
            else -> throw IllegalAccessException("This type can be saved into Preferences")
        }
        res as U
    }

    private fun  putPreference(name: String, value: U) = with(prefs.edit())
    {
        when(value)
        {
            is Long -> putLong(name, value)
            is String -> putString(name, value)
            is Int -> putInt(name, value)
            is Boolean -> putBoolean(name, value)
            is Float -> putFloat(name, value)
            else -> throw IllegalAccessException("This type can be saved into Preferences")
        }.apply()
    }
}

然后像下面这样使用:

class WhateverActivity : AppCompatActivity() {

    var aInt: Int by PreferenceHelper(this, "aInt", 0)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_whatever)
    }

    fun whatever()
    {
        //会从SharedPreference区这个数据
        print(aInt)
        //会将这个数据写入SharedPreference
        aInt = 9
    }
}

属性委托要求

对于一个只读属性(即 val 声明的),委托必须提供一个名为 getValue 的函数,该函数接受以下参数:

  • thisRef: 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型,
  • property:必须是类型 KProperty<*> 或其超类型,

这个函数必须返回与属性相同的类型(或其子类型)。

对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函数接受以下参数:

  • thisRef:同 getValue(),
  • property:同 getValue(),
  • value:必须和属性同类型或者是它的超类型。

getValue() 或/和 setValue() 函数可以通过委托类的成员函数提供或者由扩展函数提供。 当你需要委托属性到原本未提供的这些函数的对象时后者会更便利。 两函数都需要用 operator 关键字来进行标记。

类的委托

类的委托模式可以很好的替代实现继承, 而 Kotlin 可以零样板代码地原生支持它,一个类Derived 可以继承Base并委托它所有的public 方法到一个指定的类:

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b

fun main(args: Array<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10
}

Derived 的超类型列表中的 by 子句表示 b 将会在 Derived 中内部存储。 并且编译器将生成转发给 b 的所有 Base 的方法。

请注意,覆盖会以你所期望的方式工作:编译器会使用你的 override 实现取代委托对象中的实现。如果我们为 Derived 添加 override fun print() { print(“abc”) },该程序会输出“abc”而不是“10”。

你可能感兴趣的:(kotlin)