Kotlin 中 的 by 是干嘛的?

围巾🧣 2024年06月20日 149次浏览

在 Kotlin 中,by 关键字用于多种情境,主要是用于委托。以下是 by 关键字的主要用法:

1. 属性委托

Kotlin 提供了一种简洁的方式将属性的责任委托给另一个对象。属性委托对于实现具有自定义行为的属性非常有用。

语法:

class Example {
    var property: Type by Delegate()
}

示例:

import kotlin.reflect.KProperty

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 感谢你将 '${property.name}' 委托给我!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value 已被赋值给 '${property.name}' 在 $thisRef.")
    }
}

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

fun main() {
    val e = Example()
    e.p = "委托"
    println(e.p)
}

2. 接口委托

Kotlin 支持将接口的实现委托给另一个对象。这在你想要将一个或多个方法的实现委托给一个辅助对象时非常有用。

语法:

interface MyInterface {
    fun myMethod()
}

class MyClass(myInterface: MyInterface) : MyInterface by myInterface

示例:

interface Printer {
    fun print()
}

class ConsolePrinter : Printer {
    override fun print() {
        println("打印到控制台")
    }
}

class FilePrinter : Printer {
    override fun print() {
        println("打印到文件")
    }
}

class Document(printer: Printer) : Printer by printer

fun main() {
    val consoleDocument = Document(ConsolePrinter())
    consoleDocument.print()

    val fileDocument = Document(FilePrinter())
    fileDocument.print()
}

3. 延迟委托

Kotlin 提供了一个内置的 lazy 函数,它将属性的初始化委托给一个 lambda 表达式,返回一个 Lazy 对象。这个 lambda 表达式只在第一次访问时执行一次。

语法:

val property: Type by lazy { initializer }

示例:

fun main() {
    val lazyValue: String by lazy {
        println("计算中!")
        "Hello"
    }

    println(lazyValue)
    println(lazyValue)
}

总结

  • 属性委托:委托属性的 getter 和 setter 逻辑。
  • 接口委托:将接口方法的实现委托给另一个对象。
  • 延迟委托:将属性的初始化委托给一个 lambda 表达式,该表达式在第一次访问时执行一次。

这些是 Kotlin 中 by 关键字的主要用法,使代码更加简洁和可重用。

拓展 by viewModels {}

在 Kotlin 中,by viewModels {} 是一种常见的委托,用于在 Android 开发中创建和获取 ViewModel 实例。这种方式利用了 Jetpack ViewModel 库提供的 viewModels 函数来简化 ViewModel 的获取和管理。下面是对 by viewModels {} 的详细分析。

1. ViewModel 概述

ViewModel 是 Android Jetpack 架构组件的一部分,旨在存储和管理与 UI 相关的数据,以便在配置更改(如旋转屏幕)时保持数据不丢失。

2. 使用 by viewModels {} 的好处

  • 简洁:减少了样板代码,简化了 ViewModel 的初始化。
  • 安全:保证在 Activity 或 Fragment 的生命周期内只创建一个 ViewModel 实例。
  • 易于测试:通过提供一个 ViewModelProvider.Factory,可以方便地替换 ViewModel 实例,从而便于测试。

3. 语法和示例

语法:

val viewModel: MyViewModel by viewModels { factory }

示例:
假设有一个 MyViewModel 类,以下是如何在一个 Fragment 中使用 by viewModels {}

class MyFragment : Fragment() {

    // 使用 `by viewModels {}` 委托创建 ViewModel 实例
    private val viewModel: MyViewModel by viewModels {
        // 提供一个 ViewModelProvider.Factory 的实例
        MyViewModelFactory(repository)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_my, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 现在可以使用 viewModel
        viewModel.someLiveData.observe(viewLifecycleOwner, Observer { data ->
            // 更新 UI
        })
    }
}

4. 详细分析

  • by 关键字by 关键字用于委托属性,将 ViewModel 的获取逻辑委托给 viewModels 函数。
  • viewModels 函数viewModels 是一个扩展函数,它返回一个 Lazy (ViewModelLazy)实例,该实例会在第一次访问时创建 ViewModel。
  • ViewModelProvider.Factory:如果需要传递自定义参数给 ViewModel,可以提供一个 ViewModelProvider.Factory 实例,该实例定义了 ViewModel 的创建逻辑。

5. 自定义 ViewModelProvider.Factory

如果 ViewModel 需要参数,可以创建一个自定义的 ViewModelProvider.Factory

class MyViewModelFactory(private val repository: MyRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MyViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

然后在 Fragment 中使用这个工厂:

class MyFragment : Fragment() {

    private val repository = MyRepository()
    private val viewModel: MyViewModel by viewModels {
        MyViewModelFactory(repository)
    }

    // ...其他代码
}

小结

  • by viewModels {} 简化了 ViewModel 的创建:减少了样板代码,避免手动调用 ViewModelProvider
  • 保证单一实例:在 Activity 或 Fragment 生命周期内保证 ViewModel 实例的唯一性。
  • 易于传递参数:通过自定义 ViewModelProvider.Factory,可以轻松传递参数给 ViewModel。