Kotlin基础(九):对象和委托

news2024/11/18 3:31:21

前言

本文主要讲解kotlin对象和委托。


Kotlin文章列表

Kotlin文章列表: 点击此处跳转查看


目录

在这里插入图片描述


1.1 对象

在Kotlin中,对象(Object)是一个具有特殊用途的单例实例。它是一种创建单个实例的方式,确保在整个应用程序中只存在一个特定对象。

对象在Kotlin中有以下特点和用途:

  1. 单例实例:对象只能有一个实例,这意味着在整个应用程序中只能使用同一个对象。
  2. 属性和方法:对象可以具有属性和方法,可以通过对象名称直接访问它们。
  3. 延迟初始化:对象的初始化是惰性的,即只有在第一次访问时才会被实例化。
  4. 替代类:对象可以用来替代传统的类实例,尤其是对于一些简单的场景。

在生活中,我们可以以"公司"为例来解释对象的概念。假设我们有一个名为"Company"的对象,它代表了一个公司。该对象可能具有属性,如公司名称、地址等,还可能具有方法,如招聘员工、发布公告等。由于一个公司在整个应用程序中应该是唯一的,我们可以使用对象来表示这个公司,以确保只有一个实例存在。

下面是使用步骤、原理、底层实现、优缺点、注意事项和应用场景的详细解释:

  1. 使用步骤:
    • 创建对象:使用object关键字创建一个对象,并指定其名称。
    • 定义属性和方法:在对象内部定义需要的属性和方法。
    • 使用对象:通过对象名称直接访问属性和方法。
  2. 原理和底层实现:
    • 对象是通过静态初始化器实现的,确保在首次访问时进行实例化。
    • Kotlin编译器会生成一个与对象名称相同的类,该类具有静态字段和静态方法。
    • 对象在内部使用了线程安全的双重校验锁定(Double-checked locking)来实现延迟初始化和保证线程安全。
  3. 优点:
    • 简单易用:使用对象可以避免手动实现单例模式的繁琐过程。
    • 延迟初始化:对象在第一次访问时才会被初始化,避免了不必要的开销。
    • 线程安全:对象的实例化使用双重校验锁定,保证了线程安全。
  4. 缺点和注意事项:
    • 对象的状态是全局的,可能会引起状态共享和数据竞争的问题,需要小心处理。
    • 对象的生命周期与应用程序相同,不能手动销毁。
  5. 应用场景:
    • 单例模式:当需要保证全局只有一个实例时,可以使用对象来实现单例模式。
    • 工具类:当有一组相关的方法需要在整个应用程序中使用时,可以将这些方法放在一个对象中。
    • 配置信息:当需要在应用程序中共享配置信息时,可以使用对象来保存配置数据。

下面是一个具体案例的完整Kotlin代码,使用对象来表示一个公司,并实现一些基本的功能:

// 定义一个Company对象
object Company {
    var name: String = ""
    var address: String = ""

    fun hireEmployee(name: String) {
        println("$name has been hired by $name")
    }

    fun postAnnouncement(announcement: String) {
        println("Announcement: $announcement")
    }
}

fun main() {
    // 使用Company对象
    Company.name = "ABC Company"
    Company.address = "123 Main Street"

    println("Company Name: ${Company.name}")
    println("Company Address: ${Company.address}")

    Company.hireEmployee("John Doe")
    Company.postAnnouncement("Welcome to our company!")

    // 对象的实例化是延迟的,这里才会初始化
    val companyName = Company.name
    println("Stored Company Name: $companyName")
}

运行结果:

Company Name: ABC Company
Company Address: 123 Main Street
John Doe has been hired by John Doe
Announcement: Welcome to our company!
Stored Company Name: ABC Company

在上述示例中,我们创建了一个名为Company的对象,并定义了name、address属性和hireEmployee、postAnnouncement方法。在main函数中,我们使用Company对象来设置公司的名称和地址,并调用对象的方法来雇佣员工和发布公告。最后,我们还演示了对象的延迟初始化特性,将公司名称存储到一个变量中并进行打印输出。


1.1.1 对象表达式

在Kotlin中,对象表达式(Object Expression)是一种在使用时创建匿名对象的方式。与对象声明不同,对象表达式用于创建临时的、只在特定上下文中使用的对象。

对象表达式具有以下特点和用途:

  1. 匿名对象:对象表达式创建的对象没有显式的名称,只能通过变量引用来使用。
  2. 局部性:对象表达式在创建时绑定到特定的上下文,并且只在该上下文中有效。
  3. 可继承性:对象表达式可以实现接口或继承类,并可以重写其方法。
  4. 替代类的实例:对象表达式可以用作替代传统类实例的简洁方式。

下面是使用步骤、原理、底层实现、优缺点、注意事项和应用场景的详细解释:

  1. 使用步骤:
    • 创建对象表达式:使用object关键字创建一个对象表达式,并定义其属性和方法。
    • 使用对象表达式:对象表达式创建的对象可以通过变量引用来使用。
  2. 原理和底层实现:
    • 对象表达式在底层会被编译成一个匿名类。
    • Kotlin编译器会为对象表达式生成一个匿名类,并在需要时实例化该类。
  3. 优点:
    • 简洁:对象表达式提供了一种快速创建临时对象的方式,避免了显式定义类的过程。
    • 灵活性:对象表达式可以在需要时实现接口或继承类,具有更大的灵活性。
  4. 缺点和注意事项:
    • 对象表达式创建的对象只在特定的上下文中有效,不能在其他地方复用。
    • 对象表达式通常用于创建临时对象,不适合长期存储或共享状态。
  5. 应用场景:
    • 创建事件监听器:在需要临时的事件监听器时,可以使用对象表达式来快速创建一个匿名对象,并实现事件处理逻辑。
    • 定义匿名类的实例:当需要为特定接口或类创建一个简单的实现时,可以使用对象表达式来创建匿名类的实例。

下面是一个具体案例的完整Kotlin代码,演示了对象表达式的使用:

// 定义一个接口
interface Greeting {
    fun greet()
}

fun main() {
    // 使用对象表达式创建一个实现Greeting接口的匿名对象
    val englishGreeting = object : Greeting {
        override fun greet() {
            println("Hello!")
        }
    }

    // 使用对象表达式创建一个实现Greeting接口的匿名对象
    val frenchGreeting = object : Greeting {
        override fun greet() {
            println("Bonjour!")
        }
    }

   

 // 调用匿名对象的方法
    englishGreeting.greet()
    frenchGreeting.greet()
}

运行结果:

Hello!
Bonjour!

在上述示例中,我们定义了一个Greeting接口,它包含了一个greet()方法。然后,我们使用对象表达式创建了两个匿名对象,分别实现了Greeting接口,并重写了其中的greet()方法。我们创建了englishGreetingfrenchGreeting两个对象来表示英语和法语的问候语。最后,我们通过调用匿名对象的greet()方法来输出问候语。请注意,这些对象是临时创建的,仅在特定的上下文中有效。


1.1.2 声明匿名对象

在 Kotlin 中,你可以使用匿名对象(Anonymous Object)来创建一个没有任何命名标识符的对象,通常用于实现某个接口或继承某个类的实例。匿名对象在一些特定的场景中很有用,例如需要创建一个临时对象或者在某个函数内部创建一个对象,而不必在全局范围内命名这个对象。

使用匿名对象的语法如下:

  1. 实现接口的匿名对象:
val interfaceInstance: SomeInterface = object : SomeInterface {
    override fun someFunction() {
        // 实现接口中的函数
    }
}
  1. 继承类的匿名对象:
val superClassInstance: SomeSuperClass = object : SomeSuperClass() {
    override fun someFunction() {
        // 重写继承类中的函数
    }
}

匿名对象创建后,你可以像普通对象一样使用它,但是由于没有命名标识符,无法在其他地方引用这个对象。因此,匿名对象主要用于那些只需要在局部范围内使用的临时对象。

下面是一个完整的示例:

interface SomeInterface {
    fun someFunction()
}

open class SomeSuperClass {
    open fun someFunction() {
        println("SomeSuperClass: someFunction()")
    }
}

fun main() {
    // 匿名对象实现接口
    val interfaceInstance: SomeInterface = object : SomeInterface {
        override fun someFunction() {
            println("Interface implementation: someFunction()")
        }
    }

    interfaceInstance.someFunction()

    // 匿名对象继承类
    val superClassInstance: SomeSuperClass = object : SomeSuperClass() {
        override fun someFunction() {
            super.someFunction() // 调用父类的函数
            println("Subclass implementation: someFunction()")
        }
    }

    superClassInstance.someFunction()
}

在上面的示例中,我们通过匿名对象分别实现了 SomeInterface 接口和继承了 SomeSuperClass 类,并对其中的函数进行了相应的实现。


1.1.3 访问封闭作用域内的变量

在Kotlin中,可以通过对象表达式或Lambda表达式来访问封闭作用域(Enclosing Scope)内的变量。这允许在嵌套函数或Lambda函数中访问包含它们的函数或作用域中的变量。

下面是使用对象表达式和Lambda表达式访问封闭作用域内变量的示例代码:

fun main() {
    val outerVariable = "Hello"

    // 使用对象表达式访问封闭作用域内的变量
    val objectExpression = object {
        fun printOuterVariable() {
            println(outerVariable)
        }
    }
    objectExpression.printOuterVariable()

    // 使用Lambda表达式访问封闭作用域内的变量
    val lambda = {
        println(outerVariable)
    }
    lambda()
}

运行结果:

Hello
Hello

在上述示例中,我们定义了一个main函数,并在函数内部声明了一个outerVariable变量。然后,我们使用对象表达式创建了一个匿名对象,其中定义了一个printOuterVariable方法,该方法可以访问外部的outerVariable变量。通过调用对象表达式中的方法,我们可以访问封闭作用域内的outerVariable变量。

接下来,我们使用Lambda表达式创建了一个匿名函数,其中输出了外部的outerVariable变量。通过调用Lambda函数,我们同样可以访问封闭作用域内的outerVariable变量。

无论是对象表达式还是Lambda表达式,它们都允许在嵌套函数或Lambda函数中访问封闭作用域内的变量。这种特性非常有用,可以在需要时方便地访问外部作用域中的变量。


1.1.4 陪伴对象

在Kotlin中,陪伴对象(Companion Object)是指在类内部定义的一个特殊对象,它与类相关联,并且可以访问类的私有成员。陪伴对象类似于Java中的静态成员,但在Kotlin中更加灵活和功能丰富。

陪伴对象具有以下特点和用途:

  1. 类关联:陪伴对象与其所在的类相关联,可以访问类的私有成员。
  2. 类级别:陪伴对象在类级别上存在,可以拥有自己的属性和方法。
  3. 命名:每个类只能有一个陪伴对象,并且可以通过Companion关键字访问。
  4. 扩展功能:陪伴对象可以扩展类的功能,类似于扩展函数。

下面是使用步骤、原理、底层实现、优缺点、注意事项和应用场景的详细解释:

  1. 使用步骤:
    • 在类内部定义陪伴对象:使用companion object关键字在类内部定义一个陪伴对象。
    • 访问陪伴对象:通过类名直接访问陪伴对象中的属性和方法。
  2. 原理和底层实现:
    • 陪伴对象在底层被编译为一个普通的类对象,与类相关联,并通过类名进行访问。
    • 陪伴对象内部的属性和方法在编译时会被转换为静态成员,与类的实例无关。
  3. 优点:
    • 访问私有成员:陪伴对象可以访问其所在类的私有成员,方便地组织和访问相关逻辑。
    • 扩展功能:陪伴对象可以添加类级别的属性和方法,为类提供额外的功能。
    • 代码组织:陪伴对象可以将相关的代码组织在一起,提高代码的可读性和可维护性。
  4. 缺点和注意事项:
    • 陪伴对象的属性和方法在编译时会被转换为静态成员,因此无法访问类的实例成员。
    • 陪伴对象的属性和方法不能被继承或覆盖。
  5. 应用场景:
    • 工厂方法:陪伴对象可以提供类似于工厂方法的功能,用于创建类的实例。
    • 静态常量:陪伴对象可以定义类的静态常量,例如数据库表名、文件路径等。
    • 工具类:陪伴对象可以用于定义类的工具方法,提供便捷的操作和功能。

下面是一个具体案例的完整Kotlin代码,演示了陪伴对象的使用:

class MyClass {
    companion object {
        private const val PREFIX = "MyClass:"

        fun createInstance(): MyClass {
            println("$PREFIX Creating an instance of MyClass")
            return MyClass()
        }
    }

    fun printMessage() {
        println("$PREFIX Printing a message from MyClass")
    }
}

fun main() {
    val obj = MyClass.createInstance()
    obj.printMessage()
}

运行结果:

MyClass: Creating an instance of MyClass
MyClass: Printing a message from MyClass

在上述示例中,我们定义了一个名为MyClass的类,并在其内部定义了一个陪伴对象。陪伴对象通过companion object关键字声明,其中定义了一个createInstance()方法用于创建MyClass的实例。此外,陪伴对象还定义了一个私有的常量PREFIX,用于辅助打印信息。

main函数中,我们通过类名直接调用陪伴对象中的createInstance()方法来创建MyClass的实例。然后,我们调用实例的printMessage()方法,输出一个来自MyClass的消息。

通过使用陪伴对象,我们可以在类内部组织和访问相关的属性和方法,同时提供类级别的功能。陪伴对象允许我们在类的上下文中使用静态属性和方法的灵活性。


1.1.5 kotlin对象在Android中的使用

在 Kotlin 中,对象(Object)是一种用来定义单例对象的特殊类。在 Android 开发中,单例对象常用于实现全局共享的数据、资源、管理器等,因为它们在应用程序的整个生命周期内只会被实例化一次。

使用 Kotlin 对象的语法很简单,只需使用 object 关键字定义即可。下面是在 Android 中使用 Kotlin 对象的几个常见用例:

1. 单例对象
单例对象可以用于全局共享数据或管理器,确保整个应用程序只有一个实例。

object DataManager {
    // 在这里实现全局共享的数据或方法
    var sharedData: String = ""
    fun doSomething() {
        // 实现某些操作
    }
}

在任何地方,你都可以直接访问 DataManager.sharedData 或者调用 DataManager.doSomething() 来使用这个单例对象。

2. 对象表达式
对象表达式允许你在需要时创建一个临时的对象,通常用于实现某个接口或继承某个类的情况。这在处理回调、监听器等场景时很有用。

interface OnItemClickListener {
    fun onItemClick(item: String)
}

class MyAdapter(private val listener: OnItemClickListener) {
    // 一些其他实现
}

// 在使用 MyAdapter 的地方,可以创建一个匿名的 OnItemClickListener 对象
val adapter = MyAdapter(object : OnItemClickListener {
    override fun onItemClick(item: String) {
        // 处理点击事件
    }
})

3. 伴生对象
伴生对象是在类内部定义的对象,类似于 Java 的静态成员。它可以用于存放与类相关的静态方法或常量。

class MyFragment : Fragment() {
    companion object {
        const val ARG_KEY = "arg_key"

        fun newInstance(): MyFragment {
            return MyFragment()
        }
    }

    // 其他代码...
}

在这个例子中,companion object 中的内容可以像静态方法一样调用,例如 MyFragment.newInstance()


1.2 委托

委托(Delegation)是一种设计模式,在Kotlin中可以通过委托属性和委托类来实现。它允许将对象的某些操作委托给另一个对象来处理,从而实现代码的重用和解耦。

下面是关于委托的介绍:

  1. 概念:委托是一种通过将操作委托给另一个对象来实现代码复用和解耦的机制。委托对象(Delegate)负责处理实际的操作,委托属性(Delegated Property)则将属性的访问和修改委托给委托对象。
  2. 作用:委托可以减少代码重复,提高代码的可维护性和可读性。通过委托,我们可以将特定的行为和功能从类中提取出来,并交给专门的对象来处理。
  3. 生活中的例子:假设你是一个项目经理,你委托你的团队成员完成一些具体的任务,然后你收集并整合他们的工作成果。在这个例子中,你是委托者,团队成员是委托对象,他们的工作成果是委托属性。
  4. 使用步骤:
    • 创建委托对象:定义一个实际处理操作的对象。
    • 创建委托属性:在类中定义委托属性,并将其初始化为委托对象。
    • 使用委托属性:通过委托属性来访问和修改属性的值。
  5. 原理和底层实现:委托属性通过将属性的访问和修改操作委托给委托对象来实现。在编译时,Kotlin编译器会自动生成委托属性的委托方法,并将其转化为对委托对象对应方法的调用。
  6. 优点:
    • 代码复用:委托可以将通用的操作和功能从类中提取出来,实现代码的复用。
    • 解耦和模块化:委托将关注点分离,使得类的职责更加清晰,提高了代码的可读性和可维护性。
  7. 缺点和注意事项:
    • 委托会引入额外的开销,因为需要维护委托对象和委托属性之间的关系。
    • 注意避免委托循环,即委托对象和委托属性相互依赖的情况。
  8. 应用场景:
    • 代码重用:当多个类具有相似的操作和功能时,可以使用委托将这些共同的部分提取出来,实现代码的重用。
    • 解耦和扩展:当需要在不修改原有类的情况下,为类添加新的行为或功能时,可以使用委托来实现解耦和模块化。

下面是一个具体案例的完整Kotlin代码,演示了委托的使用:

interface Worker {
    fun doWork()
}

class TeamMember(private val name: String) : Worker {
    override fun doWork() {
        println("$name is working on a task")
    }
}

class ProjectManager(private val worker: Worker) : Worker by worker {
    fun collectResults() {
        println("Project Manager is collecting the results")
    }
}

fun main() {
    val teamMember = TeamMember("John")
    val projectManager = ProjectManager(teamMember)

    projectManager.doWork()
    projectManager.collectResults()
}

运行结果:

John is working on a task
Project Manager is collecting the results

在上述示例中,我们定义了一个Worker接口,其中包含一个doWork()方法。然后,我们创建了一个TeamMember类,实现了Worker接口,并在doWork()方法中输出了团队成员的工作信息。

接下来,我们创建了一个ProjectManager类,它也实现了Worker接口,并通过委托属性将工作委托给了传入的worker对象。ProjectManager类还定义了一个collectResults()方法,用于收集工作结果。

main函数中,我们创建了一个TeamMember对象teamMember和一个ProjectManager对象projectManager,并将teamMember传递给projectManager的构造函数进行委托。然后,我们通过调用projectManager对象的方法来执行工作和收集结果。

通过使用委托,ProjectManager类将实际的工作操作委托给了TeamMember对象,实现了代码的重用和解耦。ProjectManager可以通过委托属性直接访问和调用TeamMember对象的方法,从而实现了功能的扩展和模块化。


1.2.1 类的委托

在Kotlin中,类的委托是一种通过将类的实现委托给其他类来实现代码重用和解耦的机制。通过类的委托,可以将一个类的接口实现委托给另一个类,从而避免代码的冗余并提高代码的可维护性。

下面是关于类的委托的介绍:

  1. 概念:类的委托是一种将类的接口实现委托给其他类来实现代码重用和解耦的机制。被委托的类负责实际的操作,委托类通过将请求转发给被委托类来实现。
  2. 作用:类的委托可以减少代码重复,提高代码的可读性和可维护性。通过委托,我们可以将通用的操作和功能从类中提取出来,并交给专门的类来处理。
  3. 使用步骤:
    • 创建被委托的类:定义一个实现接口的类,负责实际的操作。
    • 创建委托类:定义一个委托类,并将接口的实现委托给被委托的类。
    • 使用委托类:在需要使用被委托类功能的类中,将委托类作为属性,并调用委托类的方法。
  4. 原理和底层实现:在类的委托中,委托类将接口的实现委托给被委托的类,通过将请求转发给被委托类来实现。在编译时,Kotlin编译器会自动生成委托类的委托方法,并将其转化为对被委托类对应方法的调用。
  5. 优点:
    • 代码复用:类的委托可以将通用的操作和功能从类中提取出来,实现代码的复用。
    • 解耦和模块化:类的委托将关注点分离,使得类的职责更加清晰,提高了代码的可读性和可维护性。
  6. 缺点和注意事项:
    • 委托会引入额外的开销,因为需要维护委托类和被委托类之间的关系。
    • 注意避免委托循环,即委托类和被委托类相互依赖的情况。
  7. 应用场景:
    • 接口实现:当一个类需要实现多个接口时,可以使用委托将其中一个或多个接口的实现委托给其他类。
    • 装饰器模式:当需要在原有类的功能上添加额外的行为时,可以使用委托来实现装饰器模式。

下面是一个具体案例的完整Kotlin代码,演示了类的委托的使用:

interface Printer {
    fun printMessage(message: String)
}

class ConsolePrinter : Printer {
    override fun printMessage(message: String) {
        println("Printing message: $message")
    }
}

class MessagePrinter(private val printer: Printer) : Printer by printer {
    fun printDecoratedMessage(message: String) {
        val decoratedMessage = "**********\n$message\n**********"
        printMessage(decoratedMessage)
    }
}

fun main() {
    val consolePrinter = ConsolePrinter()
    val messagePrinter = MessagePrinter(consolePrinter)

    messagePrinter.printDecoratedMessage("Hello, Kotlin!")
}

运行结果:

**********
Hello, Kotlin!
**********

在上述示例中,我们定义了一个Printer接口,其中包含一个printMessage()方法。然后,我们创建了一个ConsolePrinter类,实现了Printer接口,并在printMessage()方法中打印了消息。

接下来,我们创建了一个MessagePrinter类,它也实现了Printer接口,并通过委托属性将打印消息的功能委托给了传入的printer对象。MessagePrinter类还定义了一个printDecoratedMessage()方法,用于打印装饰后的消息。

main函数中,我们创建了一个ConsolePrinter对象consolePrinter和一个MessagePrinter对象messagePrinter,并将consolePrinter传递给messagePrinter的构造函数进行委托。然后,我们通过调用messagePrinter对象的方法来打印装饰后的消息。

通过使用类的委托,MessagePrinter类将实际的打印操作委托给了ConsolePrinter对象,实现了代码的重用和解耦。MessagePrinter可以通过委托属性直接访问和调用ConsolePrinter对象的方法,从而实现了功能的扩展和模块化。


1.2.2 委托属性

在Kotlin中,委托属性(Delegated Property)是一种特殊类型的属性,它将属性的访问和修改委托给另一个对象来处理。委托属性提供了一种方便的方式来实现属性的延迟初始化、属性委托、属性拦截等功能,以减少重复的代码并提高代码的可维护性。

下面是关于委托属性的介绍:

  1. 概念:委托属性是一种将属性的访问和修改委托给其他对象来处理的属性。委托属性本身只包含一个委托对象,并通过委托对象来处理属性的操作。
  2. 作用:委托属性可以减少重复的代码,实现属性的延迟初始化、属性委托、属性拦截等功能,提高代码的可读性和可维护性。
  3. 生活中的例子:假设你是一个项目经理,你委托你的团队成员完成一些具体的任务,并在他们完成任务后收集任务结果。在这个例子中,你是委托者,团队成员是委托对象,他们的任务结果是委托属性。
  4. 使用步骤:
    • 创建委托对象:定义一个对象,负责实际处理属性的操作。
    • 创建委托属性:在类中定义委托属性,并将其初始化为委托对象。
    • 使用委托属性:通过委托属性来访问和修改属性的值。
  5. 原理和底层实现:委托属性通过将属性的访问和修改操作委托给委托对象来实现。在编译时,Kotlin编译器会自动生成委托属性的委托方法,并将其转化为对委托对象对应方法的调用。
  6. 优点:
    • 代码复用:委托属性可以将通用的属性操作从类中提取出来,实现代码的复用。
    • 延迟初始化:通过委托属性,可以实现属性的延迟初始化,只在需要时进行初始化。
    • 属性委托:委托属性可以将属性的操作委托给其他对象来处理,实现属性的委托。
  7. 注意事项:
    • 委托属性必须定义为varval,具体取决于属性是否可写。
    • 委托属性的委托对象必须提供属性的读取和写入方法。
  8. 应用场景:
    • 延迟初始化:当属性的初始化成本较高或在首次访问时才需要初始化时,可以使用委托属性实现延迟初始化。
    • 属性委托:当需要将属性的操作委托给其他对象来处理时,可以使用委托属性实现属性委托。

下面是一个延迟初始化委托属性的具体案例的完整Kotlin代码,演示了委托属性的使用:

class ExpensiveResource {
    init {
        println("Initializing expensive resource")
    }

    fun doSomething() {
        println("Doing something with expensive resource")
    }
}

class ResourceHolder {
    val resource: ExpensiveResource by lazy {
        println("Lazy initializing resource")
        ExpensiveResource()
    }
}

fun main() {
    val holder = ResourceHolder()

    println("Accessing resource for the first time:")
    holder.resource.doSomething()

    println("Accessing resource for the second time:")
    holder.resource.doSomething()
}

运行结果:

Accessing resource for the first time:
Lazy initializing resource
Initializing expensive resource
Doing something with expensive resource
Accessing resource for the second time:
Doing something with expensive resource

在上述示例中,我们定义了一个ExpensiveResource类,表示一个昂贵的资源,它在初始化时输出一条消息。

然后,我们创建了一个ResourceHolder类,它包含一个委托属性resource,类型为ExpensiveResource。属性的初始化通过lazy委托实现,即只在首次访问时进行初始化。

main函数中,我们创建了一个ResourceHolder对象holder,并通过访问holder.resource来使用资源。在第一次访问时,委托属性进行了延迟初始化,输出了相应的消息,并执行了相应的操作。在第二次访问时,委托属性已经初始化过了,不再进行初始化,直接执行操作。

通过使用委托属性的延迟初始化,我们可以避免不必要的资源初始化,提高性能和效率。只有在需要访问属性时,才进行初始化,实现了延迟加载的效果。


1.2.3 委托类的初始化函数

在Kotlin中,委托类的初始化函数(Delegate Initialization Function)指的是在创建委托类实例时调用的函数,用于初始化委托类的属性或执行其他必要的操作。委托类的初始化函数可以通过构造函数参数或自定义的初始化方法来实现。

下面是关于委托类的初始化函数的介绍:

  1. 概念:委托类的初始化函数是在创建委托类实例时调用的函数,用于初始化委托类的属性或执行其他必要的操作。
  2. 作用:委托类的初始化函数用于在委托类实例创建时进行必要的初始化,例如设置属性的初始值、执行一些准备工作等。
  3. 使用步骤:
    • 定义委托类:创建一个类,作为委托类,并定义初始化函数。
    • 实现初始化函数:在委托类中实现初始化函数,根据需要进行属性的初始化或其他操作。
    • 调用初始化函数:在创建委托类实例时,调用初始化函数来完成初始化。
  4. 原理和底层实现:委托类的初始化函数实际上就是一个普通的函数,可以通过构造函数参数传递所需的初始化值,或者通过自定义的初始化方法来完成初始化操作。
  5. 优点:
    • 灵活性:通过委托类的初始化函数,可以在创建委托类实例时执行一些定制化的初始化操作,提供更灵活的初始化方式。
    • 可维护性:将初始化逻辑封装在初始化函数中,可以提高代码的可读性和可维护性。
  6. 注意事项:
    • 初始化函数应该在创建委托类实例时被调用,以确保属性的正确初始化。
    • 初始化函数可以包含必要的参数,以传递初始化所需的值。
  7. 应用场景:
    • 属性初始化:当需要在创建委托类实例时对属性进行初始化时,可以使用委托类的初始化函数。
    • 准备工作:当需要在创建委托类实例时执行一些准备工作时,可以使用委托类的初始化函数。

下面是一个具体案例的完整Kotlin代码,演示了委托类的初始化函数的使用:

class Connection {
    init {
        println("Establishing connection...")
        // 执行一些连接相关的初始化操作
    }

    fun executeQuery(query: String) {
        println("Executing query: $query")
        // 执行查询操作
    }
}

class DatabaseClient(private val connection: Connection) {
    init {
        println("Initializing database client...")
        // 执行一些数据库客户端的初始化操作
    }

    fun performQuery(query: String) {
        connection.executeQuery(query)
    }
}

fun main() {
    val connection = Connection()
    val client = DatabaseClient(connection)

    client.performQuery("SELECT * FROM users")
}
运行结果:
Establishing connection...
Initializing database client...
Executing query: SELECT * FROM users

在上述示例中,我们定义了一个Connection类,表示数据库连接,它在初始化时输出一条消息,并执行一些连接相关的初始化操作。

然后,我们创建了一个DatabaseClient类,它包含一个委托属性connection,类型为Connection。在DatabaseClient的初始化函数中,我们输出了一条消息,并执行了一些数据库客户端的初始化操作。

main函数中,我们首先创建了一个Connection对象connection,它会执行连接相关的初始化操作。然后,我们创建了一个DatabaseClient对象client,将connection对象作为参数传递给DatabaseClient的构造函数,进行委托。

最后,我们通过调用client对象的performQuery方法来执行查询操作,该方法内部会委托调用connection对象的executeQuery方法。

通过使用委托类的初始化函数,我们可以在创建委托类实例时执行一些初始化操作,确保属性的正确初始化和其他必要的准备工作。这样可以提高代码的可读性和可维护性,同时提供了灵活的初始化方式。


1.2.4 委托的前提条件

在 Kotlin 中,委托是一种强大的设计模式,允许一个类将某些特定的功能实现委托给另一个类,从而减少代码重复和增加代码复用性。使用委托,可以通过组合来实现类似于多继承的效果。

然而,委托需要满足一些前提条件才能使用:

  1. 接口委托:被委托的类必须实现一个接口,委托类则要通过实现同样的接口来提供委托功能。
  2. 属性委托:被委托的类必须是属性(property)而不是普通字段(field),Kotlin 中的属性由 valvar 关键字声明。
  3. 使用关键字 by:在使用委托时,需要使用关键字 by 将委托类与被委托的类关联起来。
  4. 接口中的方法或属性需要被覆盖:被委托的类必须覆盖接口中的所有方法或属性,委托类则实现这些方法或属性的具体实现。
  5. 委托类要实例化:委托类必须在被委托的类中实例化。

下面是一个使用接口委托的简单示例:

// 定义接口
interface Printer {
    fun printMessage(message: String)
}

// 实现委托的类
class ConsolePrinter : Printer {
    override fun printMessage(message: String) {
        println("Printing: $message")
    }
}

// 被委托的类
class MessageProcessor(printer: Printer) : Printer by printer {
    // 这里不需要实现 printMessage() 方法,委托类 ConsolePrinter 将提供实现
}

fun main() {
    val consolePrinter = ConsolePrinter()
    val messageProcessor = MessageProcessor(consolePrinter)

    messageProcessor.printMessage("Hello, Kotlin!") // 使用委托进行打印
}

在上述示例中,我们定义了一个接口 Printer,一个实现了 Printer 接口的 ConsolePrinter 类,和一个被委托的 MessageProcessor 类。MessageProcessor 类通过 by 关键字将打印功能委托给 Printer 接口的实现,这里是 ConsolePrinter

总结:Kotlin 中使用委托的前提条件是,被委托的类必须实现一个接口,并通过 by 关键字将委托类与被委托的类关联起来,委托类则实现接口中的方法或属性的具体实现。


1.2.5 kotlin委托在Android中的使用

在 Android 中,Kotlin 委托是一种非常有用的设计模式,可以帮助简化代码并增加代码复用性。Kotlin 委托在 Android 中的使用主要涉及两个方面:属性委托和接口委托。

属性委托
在 Android 中,属性委托常用于简化视图绑定,比如使用 findViewById 获取视图并将其与活动(Activity)或片段(Fragment)中的属性绑定。通过使用属性委托,可以避免在每次使用视图时都手动调用 findViewById,从而简化代码并提高可读性。

Android 中,通常使用 kotlin-android-extensions 或 View Binding 来实现属性委托。这两种方式都能够自动处理视图绑定,让你能够像访问普通属性一样访问视图。例如:

  1. 使用 kotlin-android-extensions
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 不再需要手动调用 findViewById
        textView.text = "Hello, Kotlin with Android Extensions!"
    }
}
  1. 使用 View Binding:
private lateinit var binding: ActivityMainBinding

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view = binding.root
        setContentView(view)

        // 通过 View Binding 访问视图
        binding.textView.text = "Hello, Kotlin with View Binding!"
    }
}

接口委托
在 Android 中,接口委托常用于实现一些通用的行为或功能,并在多个类之间共享这些实现。一个常见的例子是 RecyclerView 的适配器(Adapter)。

在 RecyclerView 的适配器中,可以使用接口委托来实现视图的创建和数据绑定逻辑,以及处理点击事件等。通过委托,可以将这些逻辑从活动(Activity)或片段(Fragment)中分离出来,使得代码更加模块化和易于维护。

以下是 RecyclerView 适配器的简化示例:

class MyAdapter(private val itemList: List<String>) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
        return ViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = itemList[position]
        holder.bind(item)
    }

    override fun getItemCount() = itemList.size

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(item: String) {
            itemView.textView.text = item

            // 处理点击事件等其他逻辑
            itemView.setOnClickListener {
                // 处理点击事件
            }
        }
    }
}

在上面的示例中,我们使用了接口委托来实现 RecyclerView 适配器,将视图的创建和数据绑定逻辑委托给 onCreateViewHolderonBindViewHolder 方法。这样可以使得适配器代码更加简洁和可维护。

总结:在 Android 中,Kotlin 委托主要用于简化属性绑定和实现通用功能。属性委托可以通过 kotlin-android-extensions 或 View Binding 来实现自动视图绑定,而接口委托可以用于实现通用逻辑,例如 RecyclerView 的适配器。使用委托能够提高代码的可读性和可维护性,同时减少重复性代码。


1.3 标准委托

在Kotlin中,标准委托(Standard Delegates)是一组内置的委托属性,用于简化常见的委托模式的实现。它们提供了一种简单而方便的方式来实现属性的委托,并解决了一些常见的问题。

下面是关于标准委托的介绍:

  1. 概念:标准委托是一组内置的委托属性,用于简化常见的委托模式的实现。
  2. 作用:标准委托提供了一种简单而方便的方式来实现属性的委托,可以减少重复的代码,并解决一些常见的问题,如懒加载、观察属性变化等。
  3. 生活中的例子:想象一个场景,你需要在一个对象中缓存某个值,当需要访问该值时,如果已经缓存过,则直接返回缓存的值,如果没有缓存,则计算出值并进行缓存。这种情况下,你可以使用标准委托中的lazy委托来实现延迟初始化和缓存功能。
  4. 使用步骤:
    • 导入标准委托:在代码中导入标准委托的相关类或函数,它们位于kotlin.properties包中。
    • 应用标准委托:在需要委托属性的地方,使用标准委托来声明属性,指定委托对象和相关参数。
  5. 原理和底层实现:标准委托是通过封装常见的委托模式和功能,使用特定的委托类或函数来实现的。底层实现利用了属性委托的语法和机制。
  6. 优点:
    • 简化代码:标准委托提供了一种简单的方式来实现属性的委托,减少了重复的代码和样板代码的编写。
    • 提供常见功能:标准委托解决了一些常见的问题,如延迟初始化、懒加载、属性变化观察等。
  7. 注意事项:
    • 熟悉标准委托的特性和使用方式,以确保正确地选择和应用适合的标准委托。
    • 注意标准委托的适用范围和限制,避免滥用和误用。
  8. 应用场景:
    • 延迟初始化:当需要在首次访问时进行延迟初始化时,可以使用lazy委托。
    • 懒加载:当需要在首次访问时进行懒加载时,可以使用lazy委托。
    • 属性变化观察:当需要观察属性变化并执行相应操作时,可以使用observable委托。

下面是一个具体案例的完整Kotlin代码,演示了标准委托的使用:

import kotlin.properties.Delegates

class User {
    var name: String by Delegates.observable("<no name>") { property, oldValue, newValue ->
        println("Property '${property.name}' changed from '$oldValue' to '$newValue'")
    }
}

fun main() {
    val user = User()
    user.name = "Alice" // 属性变化触发观察者函数
    user.name = "Bob"   // 属性变化触发观察者函数
}

运行结果:

Property 'name' changed from '<no name>' to 'Alice'
Property 'name' changed from 'Alice' to 'Bob'

在上述示例中,我们定义了一个User类,其中的name属性使用了Delegates.observable标准委托。该委托允许我们在属性的值发生变化时触发观察者函数。

main函数中,我们创建了一个User对象user。然后,我们将username属性分别设置为"Alice"和"Bob",每次属性变化都会触发观察者函数,并输出属性变化的信息。

通过使用Delegates.observable标准委托,我们实现了属性变化的观察功能,而无需显式编写观察者模式的相关代码。这样可以减少了重复的代码,并提供了一种简单而方便的方式来实现属性的委托和观察。


1.3.1 惰性装载

在 Kotlin 中,惰性装载(Lazy Loading)是一种常见的使用标准委托的技术,它允许在首次访问时延迟初始化对象,从而提高性能和节省资源。惰性装载通常用于需要耗时初始化或昂贵资源的情况。

下面是关于惰性装载的介绍:

  1. 概念:惰性装载是一种延迟初始化对象的技术,即在首次访问时才进行对象的创建和初始化。
  2. 作用:惰性装载可以提高性能和节省资源,特别是在对象初始化耗时较长或需要昂贵资源的情况下。
  3. 生活中的例子:想象一个场景,你有一个数据库连接对象,创建和初始化该对象可能需要花费一定的时间和资源。如果你的应用程序在启动时不需要立即使用该数据库连接,那么可以使用惰性装载来延迟初始化该对象,直到第一次需要使用它的时候才进行创建和初始化。
  4. 使用步骤:
    • 导入标准委托:在代码中导入标准委托的相关类或函数,lazy 是惰性装载的标准委托。
    • 应用惰性装载:在需要进行惰性装载的属性上使用 lazy 委托来声明,并提供一个 lambda 表达式作为惰性初始化逻辑。
  5. 原理和底层实现:惰性装载使用了标准委托中的 lazy 委托,该委托通过线程安全的方式来保证对象只会被初始化一次。
  6. 优点:
    • 延迟初始化:惰性装载允许在首次访问时才进行对象的创建和初始化,避免了不必要的初始化操作。
    • 提高性能和节省资源:在耗时初始化或昂贵资源的情况下,惰性装载可以提高性能和节省系统资源。
  7. 注意事项:
    • 惰性装载适用于那些在首次访问之前不需要立即初始化的对象。
    • 需要注意惰性装载的线程安全性,确保在多线程环境下也能正确进行初始化。
  8. 应用场景:
    • 耗时初始化的对象:当对象的初始化过程耗时较长时,可以使用惰性装载来推迟初始化,直到需要使用该对象时才进行初始化。
    • 昂贵资源的对象:当对象需要昂贵的资源,如数据库连接、文件加载等时,可以使用惰性装载来避免不必要的资源消耗。

下面是一个具体案例的完整 Kotlin 代码,演示了惰性装载的使用:

val lazyValue: String by lazy {
    println("Initializing lazyValue")
    "Hello, Lazy!"
}

fun main() {
    println("Before accessing lazyValue")
    println(lazyValue) // 首次访问时进行初始化
    println("After accessing lazyValue")
}

运行结果:

Before accessing lazyValue
Initializing lazyValue
Hello, Lazy!
After accessing lazyValue

在上述示例中,我们使用 lazy 委托来实现惰性装载。声明了一个名为 lazyValue 的属性,该属性使用 lazy 委托进行惰性初始化。我们提供了一个 lambda 表达式作为惰性初始化逻辑,在首次访问 lazyValue 时会执行该 lambda 表达式进行初始化。

main 函数中,我们首先输出 “Before accessing lazyValue”,然后访问 lazyValue 属性,它会触发初始化,输出 “Initializing lazyValue”。接着打印出 lazyValue 的值 “Hello, Lazy!”,最后输出 “After accessing lazyValue”。

通过使用 lazy 委托,我们实现了惰性装载的功能,lazyValue 属性在首次访问时进行初始化,避免了不必要的初始化操作,从而提高了性能和节省了资源。


1.3.2 可观察属性

在 Kotlin 中,可观察属性(Observable Properties)是一种标准委托,它允许我们在属性值发生变化时监听并做出相应的反应。通过可观察属性,我们可以轻松地实现属性值的观察和响应。

下面是关于可观察属性的介绍:

  1. 概念:可观察属性是一种标准委托,用于监听属性值的变化并做出相应的反应。
  2. 作用:可观察属性提供了一种简单的方式来观察和响应属性值的变化,可以在属性发生变化时执行自定义的操作。
  3. 生活中的例子:想象一个场景,你有一个温度传感器,它会定期检测当前的温度值。你希望能够实时监控温度的变化,并在温度超过某个阈值时触发报警。这种情况下,你可以使用可观察属性来监听温度值的变化,并在超过阈值时执行报警操作。
  4. 使用步骤:
    • 导入标准委托:在代码中导入标准委托的相关类或函数,Delegates.observable 是可观察属性的标准委托。
    • 应用可观察属性:在需要观察的属性上使用 Delegates.observable 委托来声明,并提供一个 lambda 表达式作为属性变化的回调函数。
  5. 原理和底层实现:可观察属性使用了标准委托中的 Delegates.observable 委托,该委托通过在属性赋值时进行拦截和回调函数的调用来监听属性值的变化。
  6. 优点:
    • 简化代码:可观察属性提供了一种简单的方式来监听属性值的变化,避免了手动编写监听器的复杂逻辑。
    • 定制化操作:可以根据需求在属性值变化时执行自定义的操作,实现灵活的响应机制。
  7. 注意事项:
    • 可观察属性适用于那些需要在属性值变化时执行操作的场景。
    • 需要注意回调函数的执行时机和顺序,确保在合适的时机执行相应的操作。
  8. 应用场景:
    • 属性变化的监听和响应:当需要在属性值变化时执行自定义操作时,可以使用可观察属性来实现属性的监听和响应。

下面是一个具体案例的完整 Kotlin 代码,演示了可观察属性的使用:

import kotlin.properties.Delegates

class TemperatureSensor {
    var temperature:

 Int by Delegates.observable(0) { property, oldValue, newValue ->
        println("Temperature changed from $oldValue°C to $newValue°C")
        if (newValue > 30) {
            println("Temperature too high! Triggering alarm...")
            // 触发报警逻辑
        }
    }
}

fun main() {
    val sensor = TemperatureSensor()
    sensor.temperature = 25 // 温度变化触发监听器,输出温度变化信息
    sensor.temperature = 35 // 温度变化触发监听器,输出温度变化信息并触发报警
}

运行结果:

Temperature changed from 0°C to 25°C
Temperature changed from 25°C to 35°C
Temperature too high! Triggering alarm...

在上述示例中,我们定义了一个 TemperatureSensor 类,其中的 temperature 属性使用了 Delegates.observable 标准委托。该委托允许我们在属性值发生变化时触发回调函数,并在回调函数中进行相应的操作。

main 函数中,我们创建了一个 TemperatureSensor 对象 sensor。然后,我们将 sensortemperature 属性分别设置为 25 和 35,每次属性变化都会触发回调函数,并输出温度变化的信息。当温度超过 30 时,还会触发报警操作。

通过使用 Delegates.observable 标准委托,我们实现了可观察属性的功能,可以轻松地监听属性值的变化,并在需要时执行自定义的操作。这样可以提供灵活的属性监控和响应机制,简化了代码的编写和维护。


1.3.3 阻止属性的赋值操作

在 Kotlin 中,阻止属性的赋值操作是一种常见的需求,可以通过使用标准委托中的 ReadOnlyProperty 来实现。这种委托可以确保属性只能在初始化时被赋值一次,并阻止后续的赋值操作。

下面是关于阻止属性赋值的介绍:

  1. 概念:阻止属性赋值是一种使用标准委托的技术,用于确保属性只能在初始化时被赋值一次,并阻止后续的赋值操作。
  2. 作用:阻止属性赋值可以保护属性的不可变性,防止在初始化后被意外地修改。
  3. 生活中的例子:想象一个场景,你有一个订单类,其中的订单号是在创建订单时自动生成的,并且不能在后续修改。这种情况下,你可以使用阻止属性赋值的技术,确保订单号只能在初始化时被赋值一次,并阻止后续的修改操作。
  4. 使用步骤:
    • 导入标准委托:在代码中导入标准委托的相关类或函数,ReadOnlyProperty 是用于阻止属性赋值的标准委托。
    • 应用阻止属性赋值:在需要阻止赋值的属性上使用 ReadOnlyProperty 委托来声明,并提供一个初始化值。
  5. 原理和底层实现:阻止属性赋值的委托会在初始化时将属性值保存起来,并在后续的赋值操作中抛出异常,阻止属性的修改。
  6. 优点:
    • 保护属性的不可变性:阻止属性赋值可以确保属性只能在初始化时被赋值一次,并阻止后续的修改操作。
  7. 注意事项:
    • 阻止属性赋值适用于需要保护属性不被修改的场景。
    • 需要注意在初始化时正确赋值属性,并避免后续的赋值操作。
  8. 应用场景:
    • 不可变属性:当需要确保属性在初始化后不被修改时,可以使用阻止属性赋值的技术。

下面是一个具体案例的完整 Kotlin 代码,演示了阻止属性赋值的使用:

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class Order {
    val orderNumber: Int by ReadOnlyProperty { _, property ->
        if (property.name != "orderNumber") {
            throw UnsupportedOperationException("Cannot modify property ${property.name}")
        }
        12345 // 初始化值
    }
}

fun main() {
    val order = Order()
    println(order.orderNumber) // 输出:12345

    // 尝试修改属性值,将会抛出异常
    order.orderNumber = 67890 // 抛出 UnsupportedOperationException
}

运行结果:

12345
Exception in thread "main" java.lang.UnsupportedOperationException: Cannot modify property orderNumber

在上述示例中,我们定义了一个 Order 类,其中的 orderNumber 属性使用了 ReadOnlyProperty 标准委托。该委托确保属性只能在初始化时被赋值一次,并阻止后续的赋值操作。

main 函数中,我们创建了一个 Order 对象 order。然后,我们打印出 orderorderNumber 属性的值,输出为 12345。接着,我们尝试修改 orderNumber 的值为 67890,但这会导致抛出 UnsupportedOperationException 异常,表示属性不可修改。

通过使用 ReadOnlyProperty 标准委托,我们实现了阻止属性赋值的功能,确保属性只能在初始化时被赋值一次,并阻止后续的修改操作。这样可以保护属性的不可变性,提高代码的可靠性和安全性。


1.3.4 Map委托

Map 委托是 Kotlin 中的标准委托之一,它允许我们将属性的存储和访问委托给一个 Map 对象。通过使用 Map 委托,我们可以方便地将属性与 Map 中的键值对关联起来,实现属性的动态存储和访问。

下面是关于 Map 委托的介绍:

  1. 概念:Map 委托是一种标准委托,它允许我们将属性的存储和访问委托给一个 Map 对象。
  2. 作用:Map 委托提供了一种便捷的方式来将属性与 Map 中的键值对关联起来,实现属性的动态存储和访问。
  3. 生活中的例子:想象一个场景,你有一个配置文件,其中包含了一些属性和对应的值。你希望能够以属性的形式访问这些配置值,同时能够动态添加或修改配置项。这种情况下,你可以使用 Map 委托,将属性与配置文件中的键值对关联起来。
  4. 使用步骤:
    • 创建一个 Map 对象:创建一个可包含属性键值对的 Map 对象,可以是可变的 MutableMap 或不可变的 Map
    • 应用 Map 委托:在需要关联的属性上使用 Map 委托来声明,提供对应的 Map 对象和属性键。
  5. 原理和底层实现:Map 委托通过在属性的存储和访问过程中,将相关操作委托给关联的 Map 对象来实现。
  6. 优点:
    • 动态属性存储:通过 Map 委托,可以将属性的存储和访问与动态的 Map 对象关联起来,实现灵活的属性存储和访问。
    • 简化配置管理:使用 Map 委托可以方便地管理配置文件,以属性的形式访问配置项。
  7. 注意事项:
    • 确保 Map 中包含了与属性关联的键值对,否则会导致访问属性时出现异常。
    • 可根据需要选择使用可变的 MutableMap 或不可变的 Map,根据是否允许修改属性值来选择合适的 Map 类型。
  8. 应用场景:
    • 属性动态存储:当属性的存储和访问需要与动态的 Map 对象关联时,可以使用 Map 委托实现属性的动态存储和访问。

下面是一个具体案例的完整 Kotlin 代码,演示了 Map 委托的使用:

import kotlin.reflect.KProperty

class ConfigDelegate(private val config: Map<String, String>) {
    operator fun getValue(thisRef: Any?,

 property: KProperty<*>): String {
        return config[property.name] ?: throw IllegalArgumentException("Property ${property.name} not found in config")
    }
}

class AppConfig {
    private val configMap = mapOf(
        "apiUrl" to "https://api.example.com",
        "apiKey" to "your-api-key"
    )

    val apiUrl: String by ConfigDelegate(configMap)
    val apiKey: String by ConfigDelegate(configMap)
}

fun main() {
    val appConfig = AppConfig()

    println(appConfig.apiUrl) // 输出:https://api.example.com
    println(appConfig.apiKey) // 输出:your-api-key
}

运行结果:

https://api.example.com
your-api-key

在上述示例中,我们定义了一个 ConfigDelegate 类,它实现了 getValue 操作符函数,用于从关联的 Map 中获取属性的值。

然后,我们创建了一个 AppConfig 类,其中的 apiUrlapiKey 属性使用了 ConfigDelegate Map 委托,关联了一个名为 configMap 的 Map 对象。

main 函数中,我们创建了一个 AppConfig 对象 appConfig,然后通过访问 appConfig.apiUrlappConfig.apiKey 属性,分别获取了配置中的 URL 和 API Key,并将其打印出来。

通过使用 Map 委托,我们实现了属性的动态存储和访问,将属性与 Map 中的键值对关联起来。这样可以方便地管理配置文件、实现动态属性存储等场景。


1.3.5 MutableMap委托

MutableMap 委托是 Kotlin 中的标准委托之一,它允许我们将属性的存储和访问委托给一个可变的 Map 对象。通过使用 MutableMap 委托,我们可以方便地将属性与 MutableMap 中的键值对关联起来,实现属性的动态存储和访问,并且允许修改属性的值。

下面是关于 MutableMap 委托的介绍:

  1. 概念:MutableMap 委托是一种标准委托,它允许我们将属性的存储和访问委托给一个可变的 Map 对象。
  2. 作用:MutableMap 委托提供了一种便捷的方式来将属性与 MutableMap 中的键值对关联起来,实现属性的动态存储和访问,并且允许修改属性的值。
  3. 生活中的例子:想象一个场景,你有一个用户类,其中的属性包括用户ID、用户名等。你希望能够以属性的形式访问和修改这些用户属性,并且能够动态地添加或删除属性。这种情况下,你可以使用 MutableMap 委托,将属性与一个可变的 Map 对象关联起来,实现属性的动态存储和访问。
  4. 使用步骤:
    • 创建一个 MutableMap 对象:创建一个可变的 MutableMap 对象,用于存储属性的键值对。
    • 应用 MutableMap 委托:在需要关联的属性上使用 MutableMap 委托来声明,提供对应的 MutableMap 对象和属性键。
  5. 原理和底层实现:MutableMap 委托通过在属性的存储和访问过程中,将相关操作委托给关联的MutableMap 对象来实现。
  6. 优点:
    • 动态属性存储:通过 MutableMap 委托,可以将属性的存储和访问与动态的 MutableMap 对象关联起来,实现灵活的属性存储和访问,并允许修改属性的值。
  7. 注意事项:
    • 确保 MutableMap 中包含了与属性关联的键值对,否则可能会导致访问属性时出现异常。
    • 需要注意在修改属性值时,同时更新 MutableMap 中对应的键值对。
  8. 应用场景:
    • 动态属性存储和访问:当属性的存储和访问需要与动态的 MutableMap 对象关联,并且允许修改属性的值时,可以使用 MutableMap 委托实现动态属性的存储和访问。

下面是一个具体案例的完整 Kotlin 代码,演示了 MutableMap 委托的使用:

import kotlin.reflect.KProperty

class User

 {
    private val properties = mutableMapOf<String, Any?>()

    operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T {
        return properties[property.name] as? T ?: throw IllegalArgumentException("Property ${property.name} not found")
    }

    operator fun <T> setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        properties[property.name] = value
    }
}

fun main() {
    val user = User()

    user.name = "Alice"
    user.age = 25

    println(user.name) // 输出:Alice
    println(user.age) // 输出:25
}

运行结果:

Alice
25

在上述示例中,我们定义了一个 User 类,其中的属性存储在一个可变的 MutableMap 对象 properties 中。

通过实现 getValuesetValue 操作符函数,我们将属性的存储和访问委托给 properties MutableMap 对象。

main 函数中,我们创建了一个 User 对象 user。然后,我们使用属性赋值的方式给 nameage 属性赋值。接着,我们通过属性访问的方式获取 nameage 的值,并将其打印出来。

通过使用 MutableMap 委托,我们实现了属性的动态存储和访问,并且允许修改属性的值。这样可以方便地管理动态属性、实现属性的灵活存储和访问。


1.3.6 kotlin标准委托在Android中的使用

在 Kotlin 中,标准委托是一组委托属性,包括 lazyobservablevetoable 等。这些标准委托属性在 Android 中也同样适用,它们可以帮助简化代码并增加代码的可读性和可维护性。

以下是在 Android 中使用 Kotlin 标准委托的示例:

1. lazy 委托
lazy 委托用于实现延迟初始化,直到第一次访问属性时才会进行初始化。这在 Android 中经常用于初始化视图或执行一些耗时的操作,避免不必要的初始化和资源浪费。

class MainActivity : AppCompatActivity() {
    // 使用 lazy 委托来延迟初始化视图
    private val textView: TextView by lazy {
        findViewById(R.id.textView)
    }

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

        // 第一次访问 textView 时进行初始化
        textView.text = "Hello, Kotlin with lazy delegate!"
    }
}

2. observable 委托
observable 委托用于监听属性的变化,并在属性值发生变化时执行相应的操作。这在 Android 中可以用于实现视图与数据的绑定,当数据变化时自动更新视图。

class UserViewModel : ViewModel() {
    // 使用 observable 委托来监听 userName 属性的变化
    var userName: String by Delegates.observable("") { _, oldValue, newValue ->
        // 属性值发生变化时,执行相应的操作,例如更新视图
        updateView(newValue)
    }

    private fun updateView(newUserName: String) {
        // 更新视图的逻辑
    }
}

3. vetoable 委托
vetoable 委托用于在属性赋值之前进行额外的校验或操作,并可以决定是否接受新的属性值。

class SettingsFragment : Fragment() {
    // 使用 vetoable 委托来限制最大文本长度为 10
    var editTextContent: String by Delegates.vetoable("") { _, _, newValue ->
        newValue.length <= 10
    }

    // 其他代码...
}

在上述示例中,如果尝试将 editTextContent 属性赋值为超过 10 个字符的文本,委托将拒绝接受这个新值并保持属性原有的值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/814049.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C语言:扫雷(递归+清屏)详细讲解

目录 一.前言 二.功能功能实现 1.游戏菜单/雷盘定义 menu: 雷盘定义&#xff1a; 2.定义布局(数组)/初始化雷盘 数组&#xff1a; 初始化雷盘: 3.打印棋盘 4.布置雷&#xff08;利用随机数&#xff09; 5.排查雷(判断周围雷) 1.判断周围雷数&#xff1a; 2.递归排查…

卡特兰数 公式及其应用

卡特兰数可用于两种场景&#xff08;编程&#xff09; n个元素入栈&#xff0c;共有几种出栈方法n个不同的元素可以组成多少种不同形态的二叉树 卡特兰数的公式是 比如说&#xff1a; 5个元素入栈 那么一共有 种出栈方法 再比如说&#xff08;干咳一声&#xff09; 有4个不…

【雕爷学编程】MicroPython动手做(20)——掌控板之三轴加速度3

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

读发布!设计与部署稳定的分布式系统(第2版)笔记27_安全性下

1. 安全配置出现失误 1.1. 攻击者已经通过使用开箱默认的admin登录名和密码&#xff0c;进入了不少应用程序、网络设备和数据库 1.2. 出现配置的遗漏 1.2.1. 服务器默认启用不需要的特性 1.2.1.1. 我们忘记&#xff08;或不知道&#xff09;禁用它们&#xff0c;从而开放了…

C++ 多线程编程导论(下)

文章目录 参考资料线程安全&#xff08;续&#xff09;门闩与屏障——latch 对象与 barrier 对象门闩&#xff08;latch&#xff09;屏障&#xff08;barrier&#xff09; 一次性调用——once_flag 对象与 call_once 函数 异步任务未来与承诺——future 对象与 promise 对象fut…

Ubuntu网络设置之固定IP详解

尊敬的家人们&#xff0c;欢迎观看我的文章&#xff01;今天&#xff0c;我们将为您介绍Ubuntu22.04操作系统中固定IP的设置方法&#xff0c;帮助您更好地管理网络连接并提高网络稳定性。 什么是固定IP&#xff1f; 在网络中&#xff0c;IP地址是设备在网络上的唯一标识。通常…

用html+javascript打造公文一键排版系统9:主送机关排版

一、主送机关的规定 公文一般在标题和正文之间还有主送机关&#xff0c;相关规定为&#xff1a; 主送机关 编排于标题下空一行位置&#xff0c;居左顶格&#xff0c;回行时仍顶格&#xff0c;最后一个机关名称后标全角冒号。如主送机关名称过多导致公文首页不能显示正文时&…

哨兵模式原理

哨兵模式原理 一、定义二、作用三、故障转移机制主节点的选举: 哨兵的模式一、哨兵对主从复制集群进行监控二、哨兵与哨兵之间互相进行监控三、监控的目的 故障切换的原理?cluster模式cluster模式同步两种方式 一、定义 哨兵(sentinel):是一个分布式系统&#xff0c;用于对主…

pytest 入门

1,安装pytest 打开终端或命令提示符窗口,在终端中运行以下命令来安装pytest: pip install pytestpip install -i https://pypi.tuna.tsinghua.edu.cn/simple pytest 确保您的系统上已经安装了Python。您可以在终端中运行以下命令来检查Python的安装情况: pytest --version…

MATLAB | 如何绘制这样的描边散点图?

part.-1 前前言 最近略忙可能更新的内容会比较简单&#xff0c;见谅哇&#xff0c;今日更新内容&#xff1a; part.0 前言 看到gzhBYtools科研笔记(推荐大家可以去瞅瞅&#xff0c;有很多有意思的图形的R语言复现&#xff01;&#xff01;)做了这样一张图&#xff1a; 感觉很…

RK3588平台开发系列讲解(LCD篇)FrameBuffer 操作步骤

文章目录 一、FrameBuffer 介绍二、屏幕参数信息的获取三、刷新 FrameBuffer四、FrameBuffer 例程沉淀、分享、成长,让自己和他人都能有所收获!😄 📢在应用程序中,操作/dev/fbX 的一般步骤进行介绍。 打开 FrameBuffer 设备;获取 FrameBuffer 设备的固定信息和可变信息;…

生成对抗网络DCGAN学习实践

在AI内容生成领域&#xff0c;有三种常见的AI模型技术&#xff1a;GAN、VAE、Diffusion。其中&#xff0c;Diffusion是较新的技术&#xff0c;相关资料较为稀缺。VAE通常更多用于压缩任务&#xff0c;而GAN由于其问世较早&#xff0c;相关的开源项目和科普文章也更加全面&#…

809协议服务端程序解码程序

809协议服务端程序解码程序 目录概述需求&#xff1a; 设计思路实现思路分析1.服务端2.code: 拓展实现性能参数测试&#xff1a;1.功能测试 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip…

【LeetCode】探索杨辉三角模型

一、题目描述 力扣原题 首先我们要来了解一下题目本身在说些什么&#xff0c;通过下方的动图我们可以更加清楚地看到杨辉三角是怎样一步步生成的。给到的示例中我们通过输入杨辉三角的行数&#xff0c;然后通过计算得到这个杨辉三角的每一行是什么具体的数值 二、模型选择 首先…

大数据技术之ClickHouse---入门篇---介绍

星光下的赶路人star的个人主页 一棵树长到它想长到的高度之后&#xff0c;它才知道怎样的空气适合它 文章目录 1、Clickhouse入门1.1 什么是Clickhouse1.1.1 Clickhouse的特点1.1.1.1 列示储存1.1.1.2 DBMS的功能1.1.1.3 多样化引擎1.1.1.4 高吞吐写入能力1.1.1.5 数据分区与线…

JAVA SE -- 第十三天

&#xff08;全部来自“韩顺平教育”&#xff09; 集合 一、集合框架体系 集合主要是两组&#xff08;单列集合、双列集合&#xff09; Collection接口有两个重要的子接口List 、Set&#xff0c;它们的实现子类都是单列集合 Map接口的实现子类是双列集合&#xff0c;存放的…

进阶C语言——再识结构体

1 结构的基础知识 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 2 结构的声明 struct tag {member-list; }variable-list;如果下面来描述一个学生的话&#xff0c;我们会想到学生的姓名&#xff0c;成绩&#xff0c;性别等&#…

【序列化工具JdkSerialize和Protostuff】

序列化工具对比 JdkSerialize&#xff1a;java内置的序列化能将实现了Serilazable接口的对象进行序列化和反序列化&#xff0c; ObjectOutputStream的writeObject()方法可序列化对象生成字节数组 Protostuff&#xff1a;google开源的protostuff采用更为紧凑的二进制数组&#…

1-linux下mysql8.0.33安装

在互联网企业的日常工作/运维中&#xff0c;我们会经常用到mysql数据库&#xff0c;而linux下mysql的安装方式有三种&#xff1a; 1.mysql rpm安装 2.mysql二进制安装 3.mysql源码安装 今天就为大家讲讲linux下mysql8.0.33版本rpm方式的安装。 1.前提 1.1.系统版本 Cent…

目标识别数据集互相转换——xml、txt、json数据格式互转

VOC数据格式与YOLO数据格式互转 1.VOC数据格式 VOC&#xff08;Visual Object Classes&#xff09;是一个常用的计算机视觉数据集&#xff0c;它主要用于对象检测、分类和分割任务。VOC的标注格式&#xff0c;也被许多其他的数据集采用&#xff0c;因此理解这个数据格式是很重…