Kotlin中的委托属性

212 阅读7分钟

在 Kotlin 中,by 关键字通常用于委托属性(delegated properties),即一个属性的值和行为由另一个对象提供。这种机制允许你重用代码和逻辑,使得属性管理更加灵活和高效。
相关的委托类型有:
lazy,observable(通过 Delegates.observable),vetoable(通过 Delegates.vetoable),notNull(通过 Delegates.notNull),mapDelegate(通过 Delegates.mapDelegate),自定义委托。

1,lazy:
描述:lazy 委托用于延迟属性的初始化,直到第一次访问该属性时才进行初始化。

val lazyValue: String by lazy {
    "This is computed lazily"
}

适用场景:适用于开销较大的计算或初始化,这些操作可以推迟到实际需要属性值时再进行。
详细分析:
by lazy 是 Kotlin 语言中的一个特性,用于实现属性的延迟初始化。这意味着属性只有在首次被访问时才会被初始化,而不是在对象构造时就立即初始化。
这种方式有助于节省资源,特别是当属性初始化的开销较大时,或者当属性的值在对象生命周期的某个特定阶段之前都不需要时。
by lazy 的基本语法如下:

val property: Type by lazy { initializer }

property 是要延迟初始化的属性名。
Type 是属性的类型。
initializer 是一个 lambda 表达式,用于提供属性的初始值。
这个表达式在属性首次被访问时才会执行。
by lazy属性的工作原理:
当 by lazy 属性首次被访问时,Kotlin 会执行提供的 initializer lambda 表达式,并将结果存储在内部。后续的访问将直接返回这个已存储的值,而不会再次执行 initializer。 这种机制是通过属性委托来实现的。
在 Kotlin 中,by 关键字用于表示委托,而 lazy 是一个函数,它返回一个实现了延迟初始化逻辑的委托对象。这个委托对象负责处理属性的获取(get)操作,并在需要时执行初始化。
by lazy的线程安全:
by lazy 提供了线程安全的初始化选项。默认情况下,它是线程安全的,这意味着在多线程环境中,即使多个线程同时尝试初始化同一个属性,也只会有一个线程能够成功执行 initializer,而其他线程将等待这个初始化操作完成并返回结果。
适用场景:
属性初始化的开销较大,例如需要从数据库加载数据或执行复杂的计算。 属性的值在对象生命周期的某个特定阶段之前都不需要。 想避免在对象构造时就立即初始化所有属性,以节省资源或提高性能。

class DatabaseService {
    // 假设这个属性表示从数据库中加载的数据
    val data: List<String> by lazy {
        // 这里模拟从数据库加载数据的操作
        println("Loading data from database...")
        listOf("Alice", "Bob", "Charlie")
    }
}

fun main() {
    val service = DatabaseService()
    // 在这里,data 属性还没有被初始化
    println("Before accessing data...")
    // 首次访问 data 属性,触发懒加载
    println(service.data)
    // 再次访问 data 属性,返回已初始化的值
    println(service.data)
}

上面的代码中,DatabaseService 类有一个 data 属性,它使用 by lazy 进行延迟初始化。在 main 函数中,我们创建了一个 DatabaseService 实例,并在首次访问 data 属性时触发了懒加载。后续的访问将直接返回已初始化的值。

init{}与by lazy的结合

class LazyExample(val someParameter: String) {

    // 立即初始化的属性
    val eagerlyInitialized: String

    // 延迟初始化的属性
    val lazilyInitialized: String by lazy {
        // 这里是延迟初始化的代码块
        "Lazily initialized with parameter: $someParameter"
    }

    // init {} 块用于初始化立即初始化的属性
    init {
        println("Initializing LazyExample instance...")
        eagerlyInitialized = "Eagerly initialized with parameter: $someParameter"
    }

    // 构造函数之后的代码(如果有的话)...
    // 在这个例子中,构造函数没有额外的代码,因为所有的初始化工作都在 init {} 块和 by lazy 中完成了。
}

fun main() {
    println("Before creating LazyExample instance...")
    val example = LazyExample("Hello, Kotlin!")
    println("After creating LazyExample instance...")
    
    // 访问立即初始化的属性
    println(example.eagerlyInitialized)
    
    // 访问延迟初始化的属性(这将触发懒加载)
    println(example.lazilyInitialized)
    
    // 再次访问延迟初始化的属性(这次不会重新初始化)
    println(example.lazilyInitialized)
}

在上面的代码中,创建一个类 LazyExample 接受一个字符串参数 someParameter。 该类包含了两个属性:eagerlyInitialized 是一个立即初始化的属性。lazilyInitialized 是一个使用 by lazy 委托进行延迟初始化的属性。
在对象构造时,init {} 块会首先执行。在这个块中,初始化了 eagerlyInitialized 属性,并打印了一条消息。
在创建 LazyExample 实例时,eagerlyInitialized 会被立即初始化,而 lazilyInitialized 不会被初始化(因为还没有被访问)。
首次访问 lazilyInitialized 属性时,会触发懒加载,执行 by lazy 块中的代码,并初始化该属性。同时,打印了初始化后的值。
再次访问 lazilyInitialized 属性时,由于它已经被初始化过了,所以不会再次执行 by lazy 块中的代码,而是直接返回其值。

2,observable(通过 Delegates.observable):
描述:observable 委托用于创建一个可观察的属性,当属性值改变时,可以触发一个回调。 适用场景:适用于需要在属性值改变时执行某些操作的场景,如数据绑定、状态管理等。

import kotlin.properties.Delegates

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

observableValue = "new value" // 输出:initial -> new value

在上面的代码中,定义了一个名为 observableValue 的字符串变量,并通过 Delegates.observable 委托初始化,初始值为 "initial"。
Delegates.observable 接受两个参数:初始值和lambda表达式。
初始值:在这个例子中是 "initial"。
一个lambda表达式,当属性值变化时被调用。
这个lambda表达式有三个参数: prop:属性的名称。 old:属性变化前的值。 new:属性变化后的值。

也可以采用下面的方式:

class MyObservableClass {
    var observableValue: String by Delegates.observable("initial") {
            prop, old, new ->
        println("Property '$prop' changed from '$old' to '$new'")
    }

    var anotherObservableValue: Int by Delegates.observable(0) {
            prop, old, new ->
        println("Property '$prop' changed from $old to $new")
    }
}

MyObservableClass 有两个使用 Delegates.observable 委托的属性:observableValue 和 anotherObservableValue。
每个属性的观察者都会在属性值变化时打印出一条消息,包括属性的名称、旧值和新值。
在main()函数中,

val myObservableClass = MyObservableClass()
myObservableClass.observableValue="new value..."
myObservableClass.anotherObservableValue = 11

输出:
Property 'observableValue' changed from 'initial' to 'new value...'
Property 'anotherObservableValue' changed from 0 to 11

3,vetoable(通过 Delegates.vetoable):
描述:vetoable 委托类似于 observable,但允许在属性值改变之前进行拦截和拒绝。
适用场景:适用于需要验证属性值有效性的场景,如输入验证、数据完整性检查等。

import kotlin.properties.Delegates

var vetoableValue : String by Delegates.vetoable("hello") {
	prop, old, new ->
	if (new.length < 3) {
		println("value too short:$new")
        false
	} else{
		println("value not too short:$new")
		true
	}
}
vetoableValue = "world" //输出value not too short:world
vetoableValue = "ok" //输出value too short:ok

4,notNull(通过 Delegates.notNull):
描述:notNull 委托用于确保属性值在初始化后不会变为 null。

import kotlin.properties.Delegates
var notNullValue: String by Delegates.notNull<String>()
notNullValue = "set value" // 允许
// notNullValue = null // 编译错误
notNullValue = null.toString() //输出null

适用场景:适用于需要确保属性始终非空的场景,如避免空指针异常。

5,mapDelegate(通过 Delegates.mapDelegate):
描述:mapDelegate 委托用于从 Map 中读取和写入属性值。

import kotlin.properties.Delegates

class User(val map: MutableMap<String, Any?>){
    var name:String by map
    var age:Int by map

    override fun toString(): String {
        return "User(name = $name, age = $age)"
    }
}

在main函数中

val user = User(mutableMapOf(
        "name" to "Jerry",
        "age" to 34
    ))
 println(user) //输出User(name = Jerry, age = 34)
 user.age = 36
 println(user.map["age"]) //输出36

适用场景:适用于需要将属性存储在 Map 中的场景,如配置管理、动态属性存储等。

6,自定义委托:
描述:除了上述内置的委托类型,Kotlin 还允许你创建自定义的委托类型。 用法:通过实现 ReadWriteProperty 或 ReadOnlyProperty 接口,可以创建自定义的委托行为。 适用场景:适用于需要特殊行为或逻辑的自定义属性管理场景。 这些委托类型提供了灵活且强大的属性管理方式,可以根据具体需求选择合适的委托类型来实现所需的功能。

class MapDelegate<T>(private val map: MutableMap<String, Any?>, private val key: String, private val defaultValue: T) :
    ReadWriteProperty<Any?, T> {
    init {
        if (!map.containsKey(key)) {
            map[key] = defaultValue
        }
    }

    @Suppress("UNCHECKED_CAST")
    override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return map[key] as T
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
        map[key] = newValue
    }
}

data class MapUser(val map: MutableMap<String, Any?>) {
    var name: String by MapDelegate(map, "name", "")
    var age: Int by MapDelegate(map, "age", 0)

    override fun toString(): String {
        return "MapUser(name='$name', age=$age)"
    }
}

在main函数中,

val userMap = mutableMapOf<String, Any?>()
val user = MapUser(userMap)

user.name = "John"
user.age = 30

println(user) // 输出: MapUser(name='John', age=30)
println(userMap) // 输出: {name=John Doe, age=30}

MapDelegate 类负责处理属性的读写操作,它会在 MutableMap 中根据指定的 key 存储和检索值。defaultValue 参数用于在 map 中尚未包含指定 key 时提供一个初始值。
MapUser 类通过 MapDelegate 委托其 name 和 age 属性。这意味着当您访问或修改这些属性时,实际上是在操作 userMap。
MapUser 类被声明为 data 类,这意味着它会自动生成 equals、hashCode 和 toString 方法。