Kotlin 委托与扩展函数——新手入门

86 阅读3分钟

一、委托(Delegation)

1. 类委托

类委托允许将接口的实现委托给另一个对象,避免继承的局限性,适用于组合模式。

示例代码

interface Printer {
    fun printMessage(message: String)
}

class ConsolePrinter : Printer {
    override fun printMessage(message: String) {
        println("控制台输出: $message")
    }
}

// 通过委托将Printer接口的实现交给ConsolePrinter
class LogPrinter(printer: Printer) : Printer by printer {
    override fun printMessage(message: String) {
        println("日志记录开始")
        printer.printMessage(message)
        println("日志记录结束")
    }
}

fun main() {
    val consolePrinter = ConsolePrinter()
    val logPrinter = LogPrinter(consolePrinter)
    logPrinter.printMessage("测试消息")
}

使用场景

  • 需要增强或修改现有类的行为(装饰器模式)。
  • 避免多层继承带来的复杂性。

2. 属性委托

属性委托将属性的 getter/setter 逻辑委托给其他对象,Kotlin 标准库提供多种内置委托。

常用内置委托

  • lazy:延迟初始化属性。
  • observable:监听属性变化。
  • vetoable:在赋值前验证值。

示例代码

import kotlin.properties.Delegates

class Example {
    // 延迟初始化,首次访问时计算
    val lazyValue: String by lazy {
        println("计算lazyValue")
        "Hello, Lazy!"
    }

    // 监听属性变化
    var name: String by Delegates.observable("<未命名>") { _, old, new ->
        println("名称从 $old 更改为 $new")
    }

    // 仅允许非负数值
    var age: Int by Delegates.vetoable(0) { _, _, new ->
        new >= 0
    }
}

fun main() {
    val example = Example()
    println(example.lazyValue) // 输出:计算lazyValue \n Hello, Lazy!
    example.name = "Kotlin"    // 输出:名称从 <未命名> 更改为 Kotlin
    example.age = -5          // 赋值失败,age保持0
    println(example.age)       // 输出:0
}

使用场景

  • lazy:初始化成本高的资源(如数据库连接)。
  • observable:实现数据绑定或响应式UI更新。
  • vetoable:表单输入验证。

3. 自定义属性委托

通过实现 ReadWriteProperty 或 ReadOnlyProperty 接口创建自定义委托。

示例代码

import kotlin.reflect.KProperty

class FormatDelegate(private val format: String) : ReadWriteProperty<Any?, String> {
    private var value: String = ""

    override fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return value.format(format)
    }

    override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        this.value = value
    }
}

class User {
    var username: String by FormatDelegate("用户名:%s")
}

fun main() {
    val user = User()
    user.username = "Alice"
    println(user.username) // 输出:用户名:Alice
}

使用场景

  • 统一格式化字符串、数值等属性。
  • 自动加密/解密敏感数据字段。

二、扩展函数(Extension Functions)

1. 基本用法

扩展函数允许为现有类添加新方法,无需继承或修改原始类。

示例代码

// 为String添加判断是否为有效邮件的扩展函数
fun String.isValidEmail(): Boolean {
    return matches(Regex("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"))
}

// 为Int添加计算阶乘的扩展函数
fun Int.factorial(): Long {
    return if (this <= 1) 1 else this * (this - 1).factorial()
}

fun main() {
    println("user@example.com".isValidEmail()) // 输出:true
    println(5.factorial())                     // 输出:120
}

使用场景

  • 为第三方库或系统类添加实用方法。
  • 增强代码可读性,如 list.quickSort()

2. 扩展属性

类似扩展函数,可以为类添加“属性”(实际是计算属性,无幕后字段)。

示例代码

val String.firstLetter: Char
    get() = if (isEmpty()) ' ' else this[0]

fun main() {
    println("Kotlin".firstLetter) // 输出:K
}

使用场景

  • 提供基于现有属性的快捷访问方式,如 file.extension

3. 扩展与成员冲突

当扩展函数与类成员函数同名时,成员函数优先。

示例代码

class Example {
    fun print() = println("成员函数")
}

fun Example.print() = println("扩展函数")

fun main() {
    Example().print() // 输出:成员函数
}

三、注意事项

1. 委托的可见性:

  • 委托对象需在类构造时初始化。
  • 避免在委托中持有外部类的引用导致内存泄漏。

2. 扩展的限制:

  • 无法访问类的私有成员。
  • 扩展是静态解析的,不具备多态性。

3. 性能考量:

  • lazy 委托默认是线程安全的,若不需要同步可用 lazy(LazyThreadSafetyMode.NONE) 提升性能。

四、总结

特性适用场景优势
类委托组合优于继承、装饰器模式减少重复代码,提升灵活性
属性委托懒加载、属性监听、验证逻辑逻辑复用,代码简洁
扩展函数增强现有类功能、第三方库适配无侵入式扩展,提高可读性
扩展属性提供快捷访问属性简化复杂计算逻辑的调用

合理运用委托和扩展函数,能显著提升Kotlin代码的简洁性和可维护性,但需注意避免滥用导致结构混乱。

更多分享

  1. 一文吃透Kotlin中冷流(Clod Flow)和热流(Hot Flow)
  2. 一文带你吃透Kotlin协程的launch()和async()的区别
  3. 一文带你吃透接口(Interface)结合 @AutoService 与 ServiceLoader 详解
  4. Kotlin 作用域函数(let、run、with、apply、also)的使用指南
  5. 一文带你吃透Kotlin中 lateinit 和 by lazy 的区别和用法