你真的懂 Kotlin 中的 by 关键字吗?3分钟搞懂精髓

引言

在Kotlin中,by 关键字主要用于实现委托模式。委托模式是一种设计模式,它允许一个对象将部分职责委托给另一个对象。在Kotlin中,by 关键字提供了一种简洁的语法,使得委托的实现变得更加轻松。

委托模式概述

在委托模式中,有两个主要角色:

  1. 委托类(Delegated Class): 持有实际的工作对象,负责将部分职责委托给这个对象。
  2. 委托属性(Delegated Property): 在委托类中声明的属性,使用 by 关键字将其委托给其他类。

by关键字的工作原理

当使用 by 关键字将属性委托给其他类时,编译器会在后台生成一些额外的代码,实际上是将属性的 getter 和 setter 方法委托给特定的委托类。下面是一个简单的例子来说明 by 关键字的工作原理:

kotlin 复制代码
interface Printer {
    fun print(message: String)
}

class DefaultPrinter : Printer {
    override fun print(message: String) {
        println("Default: $message")
    }
}

class CustomPrinter(private val delegate: Printer) : Printer by delegate

fun main() {
    val customPrinter = CustomPrinter(DefaultPrinter())
    customPrinter.print("Hello, Kotlin!")
}

在这个例子中,CustomPrinter 类通过 by 关键字将 Printer 接口的实现委托给了 DefaultPrinter 类。编译器会生成类似下面的代码:

kotlin 复制代码
class CustomPrinter(private val delegate: Printer) : Printer {
    override fun print(message: String) {
        delegate.print(message)
    }
}

实际上,CustomPrinter 中的 print 方法被委托给了 DefaultPrinterprint 方法。

自定义委托类

除了使用接口作为委托的对象外,我们还可以自定义委托类。自定义委托类需要实现属性委托的接口,即具备 getValuesetValue 方法。以下是一个简单的自定义委托类的例子:

kotlin 复制代码
import kotlin.reflect.KProperty

class CustomDelegate {
    private var value: String = ""

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        println("Getting value: $value")
        return value
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: String) {
        println("Setting value: $newValue")
        value = newValue
    }
}

class Example {
    var customProperty: String by CustomDelegate()
}

fun main() {
    val example = Example()
    example.customProperty = "Hello, Kotlin!"
    println(example.customProperty)
}

在上面的例子中,CustomDelegate 类实现了属性委托的接口,通过重写 getValuesetValue 方法实现了属性的读取和设置。Example 类中的 customProperty 属性通过 by 关键字委托给了 CustomDelegate 类。

lazy原理

有了上面的基础,再来看lazy的实现就非常简单。

lazy 是 Kotlin 标准库中的一个函数,用于实现延迟初始化。它的主要作用是将一个 lambda 表达式作为参数传递给 lazy 函数,该 lambda 表达式将在首次访问属性时执行,并且只会执行一次。lazy 返回一个 Lazy 类型的实例,该实例包含一个被委托的属性,以及相应的初始化逻辑。

以下是 lazy 的简化实现原理,为了更好地理解,我们将采用伪代码的形式:

kotlin 复制代码
class Lazy<T>(private val initializer: () -> T) {
    private var value: T? = null
    private var isInitialized: Boolean = false

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        if (!isInitialized) {
            value = initializer()
            isInitialized = true
        }
        return value!!
    }
}

fun <T> lazy(initializer: () -> T): Lazy<T> = Lazy(initializer)

上述代码中,我们定义了一个 Lazy 类,它接受一个 lambda 表达式 initializer,这个 lambda 表达式包含了属性首次访问时的初始化逻辑。Lazy 类包含一个泛型参数 T,表示被委托的属性的类型。

  • value 存储被委托属性的值,初始值为 null。
  • isInitialized 用于追踪属性是否已经被初始化。

Lazy 类还实现了 getValue 操作符函数,这是属性委托的关键。当被委托的属性首次被访问时,getValue 函数会执行 initializer lambda 表达式,初始化属性的值,并将 isInitialized 设置为 true。之后,再次访问该属性时,直接返回已经初始化过的值。

最后,我们通过 lazy 函数创建了一个 Lazy 类的实例,用于实际的属性委托。在实际使用中,lazy 函数可以直接作为属性的委托,如下所示:

kotlin 复制代码
val myProperty: String by lazy {
    println("Initializing myProperty")
    "Hello, Kotlin!"
}

fun main() {
    println(myProperty) // 第一次访问,会执行初始化逻辑
    println(myProperty) // 后续访问,直接返回已初始化的值
}

在上述例子中,myProperty 的初始化逻辑只在首次访问时执行,之后的访问直接返回已经初始化的值。

总结

通过 by 关键字,Kotlin 提供了一种优雅而强大的委托模式实现方式。无论是通过接口还是自定义委托类,都能够轻松地实现代码的重用和解耦。了解 by 关键字的实现原理有助于更深入地理解 Kotlin 的委托模式,并在实际开发中更加灵活地运用。

推荐

android_startup: 提供一种在应用启动时能够更加简单、高效的方式来初始化组件,优化启动速度。不仅支持Jetpack App Startup的全部功能,还提供额外的同步与异步等待、线程控制与多进程支持等功能。

AwesomeGithub: 基于Github的客户端,纯练习项目,支持组件化开发,支持账户密码与认证登陆。使用Kotlin语言进行开发,项目架构是基于JetPack&DataBinding的MVVM;项目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等流行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub相对应。

android-api-analysis: 结合详细的Demo来全面解析Android相关的知识点, 帮助读者能够更快的掌握与理解所阐述的要点。

daily_algorithm: 每日一算法,由浅入深,欢迎加入一起共勉。

相关推荐
风和先行11 分钟前
adb 命令查看设备存储占用情况
android·adb
qq_3901617716 分钟前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test1 小时前
js下载excel示例demo
前端·javascript·excel
AaVictory.1 小时前
Android 开发 Java中 list实现 按照时间格式 yyyy-MM-dd HH:mm 顺序
android·java·list
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
知孤云出岫1 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web