一、回忆一下JAVA的动态代理
Java 中的动态代理是一种非常强大的机制,它允许你在运行时创建代理对象,用于代表其他对象执行一些操作。通常用于实现AOP(面向切面编程)和处理横切关注点,例如日志记录、性能监控、事务管理等。Java 的动态代理主要使用了 java.lang.reflect
包中的 Proxy
类和 InvocationHandler
接口。
下面是一个简单的 Java 动态代理示例:
interface Calculator {
int operator(int a, int b);
void print(int result);
}
class CalculatorProxy implements InvocationHandler {
private Calculator target;
public CalculatorProxy(Calculator target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
class AddCalculator implements Calculator {
@Override
public int operator(int a, int b) {
return a + b;
}
@Override
public void print(int result) {
System.out.println("a + b = " + result);
}
}
class MultiplyCalculator implements Calculator {
@Override
public int operator(int a, int b) {
return a * b;
}
@Override
public void print(int result) {
System.out.println("a * b = " + result);
}
}
class Test{
public static void main(String[] args) {
// add
Calculator addCalculator = new AddCalculator();
Calculator addProxy = (Calculator) Proxy.newProxyInstance(Calculator.class.getClassLoader(), new Class[]{Calculator.class}, new CalculatorProxy(addCalculator));
int add = addProxy.operator(2, 4);
addProxy.print(add);
// multiply
Calculator multiplyCalculator = new MultiplyCalculator();
Calculator multiplyProxy = (Calculator) Proxy.newProxyInstance(Calculator.class.getClassLoader(), new Class[]{Calculator.class}, new CalculatorProxy(multiplyCalculator));
int multiply = multiplyProxy.operator(2, 4);
multiplyProxy.print(multiply);
}
}
这是一个典型的 Java 动态代理示例,用于对不同的计算器实现进行代理。在这个示例中,我们有以下几个关键组成部分:
- 定义一个接口
Calculator
,定义了两个方法operator
和print
。 - 有两个实现了
Calculator
接口的类,AddCalculator
和MultiplyCalculator
,分别实现了operator
和print
方法。 - 然后创建了一个
CalculatorProxy
类,它实现了InvocationHandler
接口,该类的实例将用于代理Calculator
接口的实现类。 - 在
main
方法中,你首先创建了真实的Calculator
实例,然后使用Proxy.newProxyInstance
方法创建了代理对象,将它绑定到Calculator
接口上。 - 通过代理对象调用
operator
方法,实际上会触发CalculatorProxy
中的invoke
方法,该方法中通过反射调用了真实对象的operator
方法。 - 同样,通过代理对象调用
print
方法也会触发CalculatorProxy
中的invoke
方法,该方法通过反射调用了真实对象的print
方法。
这个示例演示了动态代理的基本原理,使我们可以在运行时动态地创建代理对象并在其中添加自定义逻辑。
在 Kotlin 中,代理模式的实现更加简洁,不需要涉及反射。那么接下来我们对比一下。
二、Kotlin 没有反射的动态代理
1、by 接口代理,提高代码清晰度和可读性
当使用 Kotlin 语言时,可以使用 Kotlin 标准库中的 kotlin.reflect
包提供的函数来实现动态代理,而不需要像 Java 那样使用反射。
首先,确保你的项目依赖了 Kotlin 标准库。接下来,这是一个 Kotlin 版本的示例,演示如何使用 Kotlin 的动态代理:
interface Calculator {
fun operator(a: Int, b: Int): Int
fun print(result: Int)
}
class CalculatorProxy(calculator: Calculator) : Calculator by calculator
class AddCalculator : Calculator {
override fun operator(a: Int, b: Int): Int = a + b
override fun print(result: Int) = println("a + b = $result")
}
class MultiplyCalculator : Calculator {
override fun operator(a: Int, b: Int): Int = a * b
override fun print(result: Int) = println("a * b = $result")
}
fun main() {
var addProxy = CalculatorProxy(AddCalculator())
addProxy.print(addProxy.operator(2, 4))
var multiplyProxy = CalculatorProxy(MultiplyCalculator())
multiplyProxy.print(multiplyProxy.operator(2, 4))
}
这段 Kotlin 代码展示了如何使用 by
关键字来实现接口代理,从而消除冗余的代码。我们具体来分析一下:
- 首先,我们定义了一个
Calculator
接口,其中包含两个方法:operator
用于执行数学运算,print
用于打印结果。 - 然后,我们创建了两个类
AddCalculator
和MultiplyCalculator
,它们分别实现了Calculator
接口。这两个类分别执行加法和乘法运算,并在print
方法中打印结果。 - 接着,我们创建了一个
CalculatorProxy
类,它实现了Calculator
接口,但并没有实际实现接口中的方法。相反,它通过by
关键字将接口的实现委托给传入的calculator
对象,这就是代理的关键。这意味着CalculatorProxy
对象将调用calculator
对象的方法来执行运算和打印。 - 最后,在
main
函数中,我们创建了一个AddCalculator
对象和一个MultiplyCalculator
对象,并将它们分别传递给CalculatorProxy
对象addProxy
和multiplyProxy
。然后,我们使用这些代理对象来执行数学运算并打印结果。
使用 by
关键字,我们可以在不实际实现接口方法的情况下创建代理对象,从而减少了冗余的代码,使代码更清晰易懂。这是 Kotlin 中非常有用的特性,特别是在实现接口时。
by
关键字在 Kotlin 中用于委托,它的主要作用是将接口的实现委托给另一个对象。在这里,我们将着重分析这个语法:
class CalculatorProxy(calculator: Calculator) : Calculator by calculator
by calculator
:这是委托语法的关键部分。通过使用 by
关键字,我们告诉编译器将 Calculator
接口的实现委托给 calculator
参数所引用的对象。换句话说,所有 Calculator
接口中定义的方法将由 calculator
对象来实际执行。
这种语法非常有用,因为它允许我们创建代理对象,将接口的实现委托给其他对象,从而避免了在代理类中手动实现接口的所有方法。这简化了代码并提高了可维护性,特别是当我们需要实现一些接口但只关心其中的一部分方法时。
2、在 Kotlin 中,你可以在代理类中重写接口方法以覆盖委托对象的行为
当然我们也可以在 CalculatorProxy
中重写 operator
和 print
方法,就会调用 CalculatorProxy
中的方法,而不是委托给 calculator
对象。
class CalculatorProxy(calculator: Calculator) : Calculator by calculator {
override fun operator(a: Int, b: Int): Int {
TODO("Not yet implemented")
}
override fun print(result: Int) {
TODO("Not yet implemented")
}
}
这是因为你在 CalculatorProxy
类中提供了对这些方法的自定义实现,覆盖了接口中的默认实现。
所以,如果你想让 CalculatorProxy
仍然委托给 calculator
对象,你应该避免重写这些方法,或者在实现中通过调用 calculator
对象来实现委托。如果你需要自定义行为,可以在调用 calculator
对象之前或之后添加自己的逻辑。
3、使用 by 关键字委托对象必须返回具体对象或表达式
另外,在 Kotlin 中,by
后面必须要返回一个具体的对象,也可以是表达式。比如:
class CalculatorProxy(calculator: Calculator) : Calculator by (if (BuildConfig.DEBUG) DebugCalculator() else calculator)
在示例中,使用了 by
关键字委托给了一个具体的对象。委托对象在这种情况下是一个条件表达式,根据 BuildConfig.DEBUG
的值,它要么返回 DebugCalculator()
对象,要么返回 calculator
对象,在运行时返回一个具体的对象。我们根据调试模式的不同,它会将属性的存储和访问委托给不同的对象。这是一种有用的模式,用于在不同的环境下切换属性的实现。
4、Kotlin 中的代理模式仅支持接口类型
最后,Kotlin 中的代理模式仅支持接口类型。这是因为 Kotlin 的代理模式是通过接口委托实现的,它允许一个类实现一个接口并将接口的具体实现委托给另一个类。这种方式只能用于代理接口的方法调用,而不能用于代理普通类的方法。
在 Kotlin 中,代理通过 by
关键字来实现,只能委托给接口类型的对象。如果你想代理一个普通类的方法,通常需要使用其他模式,如装饰器模式。
三、属性委托
属性委托是 Kotlin 中一个强大的特性,它允许你将属性的 getter 和 setter 方法委托给其他对象,以实现复杂的行为和逻辑。属性委托可以用于简化代码、提高可维护性,并在某些情况下引入懒加载等优化。
Kotlin 标准库提供了一些内置的属性委托,例如 lazy
、observable
和 vetoable
,同时你也可以创建自定义的属性委托。
以下是属性委托的一些重要概念和用法:
- 内置属性委托:
lazy
:用于实现懒加载,只有在首次访问属性时才会计算并初始化它。observable
:用于监视属性的变化,并在变化时执行自定义逻辑。vetoable
:类似于observable
,但允许你阻止属性的修改。
- 自定义属性委托:你可以创建自定义的属性委托,通过实现
getValue
和setValue
方法来控制属性的行为。
接下来我们逐个解析一下。
1、lazy 属性
(1)使用 lazy 属性委托实现延迟初始化
使用 lazy
属性委托实现延迟初始化。这是一种非常有用的模式,特别是当你希望在属性被访问时才进行初始化时。以下是 lazy
的基本语法和执行顺序:
基本语法:
val propertyName: T by lazy {
// 在这里执行初始化操作
// 返回值将作为属性的值
}
propertyName
:需要委托的属性的名称。lazy
闭包:这个闭包会在首次访问属性时被调用,执行初始化操作。它返回的值将作为属性的值。
执行顺序:
- 当首次访问属性时,
lazy
闭包会被调用,执行初始化操作。 - 闭包中的初始化操作只会在首次访问属性时执行一次,之后的访问直接返回已计算的值。
下面是一个使用 lazy
属性委托的示例:
val lazyValue: String by lazy {
println("使用 lazy 属性委托实现延迟初始化")
"Hello, Lazy!"
}
fun main() {
println(lazyValue) // 第一次访问属性时计算并初始化,输出 "Computed!" 和 "Hello, Lazy!"
println(lazyValue) // 第二次访问属性时直接返回已计算的值,输出 "Hello, Lazy!"
}
lazyValue
是一个延迟初始化的属性,它的值只有在首次访问时才会计算和初始化。这可以有效地节省计算资源,特别是当属性的初始化成本较高时。
在 main
函数中,首先访问了 lazyValue
属性,这导致了延迟初始化代码块的执行,输出了 “使用 lazy 属性委托实现延迟初始化” 和 “Hello, Lazy!”。然后,再次访问了相同的属性,但这次它直接返回了已计算的值,而不再执行初始化代码块。
使用 lazy 属性委托实现延迟初始化
Hello, Lazy!
Hello, Lazy!
这种模式非常有用,因为它允许你在需要时进行初始化,而不是在对象创建时,从而提高了性能和资源利用率。
(2)lazy 处理多线程初始化的问题
在 Kotlin 中,lazy
函数提供了多个重载,其中一个允许你选择 LazyThreadSafetyMode
以控制懒加载的线程安全性。LazyThreadSafetyMode
有三个选项:
SYNCHRONIZED
:这是最安全的选项,确保在多线程环境下只有一个线程初始化对象。它使用同步锁来实现线程安全。PUBLICATION
:这个选项适用于在多个线程之间共享不可变数据。它不使用锁,因此初始化可能会在多个线程中同时执行,但最终只有一个线程的结果会被返回。NONE
:这个选项不提供线程安全性保障,适用于单线程环境或者自己处理线程安全性。初始化可能会在多个线程中同时执行,不做任何同步。
lazy
函数为 Kotlin 标准包的内置函数:未指定模式则默认为 LazyThreadSafetyMode.SYNCHRONIZED
。lazy
函数定义如下所示:
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
下面是一个示例,演示如何使用 LazyThreadSafetyMode
控制懒加载的线程安全性:
val lazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
println("使用 lazy 属性委托实现延迟初始化")
"Hello, Lazy!"
}
在上面的示例中,SYNCHRONIZED
模式确保只有一个线程可以执行初始化代码,从而提供了最高级别的线程安全性。你还可以选择其他模式,根据你的需求来平衡性能和线程安全性。
我们再看一个例子:线程安全的懒汉同步块式单例。线程安全的懒汉式单例模式可以确保只有在需要时才创建实例,并且多个线程同时请求实例时不会导致多次创建。
Java代码:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
Kotlin代码:
class Singleton private constructor() {
companion object {
// 等同于 by lazy(LazyThreadSafetyMode.SYNCHRONIZED)
val instance: Singleton by lazy {
Singleton()
}
}
}
在这个代码中,我们使用了 by lazy
或者 LazyThreadSafetyMode.SYNCHRONIZED
参数来创建懒汉式单例。
by lazy
会确保在首次访问instance
属性时执行初始化操作,而且只有一个线程能够初始化实例。LazyThreadSafetyMode.SYNCHRONIZED
参数确保了线程安全性,即使在多线程环境下,只有一个线程能够执行初始化代码块。
因此,这个代码确保了在多线程环境中也能够正确创建单例实例,不会出现多次创建或竞态条件的问题。这是一种常见的线程安全的懒汉式单例实现方式。
总结,lazy
函数提供了多种方式来控制懒加载的线程安全性,使你能够根据应用程序的要求进行选择。
(3)Lazy 的工作原理是什么?
下面我们来看一下 Lazy
的工作原理,以及它的源码分析。
首先,Lazy
属性的创建通常使用 lazy { ... }
表达式,其中 { ... }
是一个 lambda 表达式,用于指定属性的初始化逻辑。例如:
val lazyValue: String by lazy {
println("初始化操作")
"Hello, Lazy!"
}
在上述代码中,lazyValue
属性将在第一次访问时执行 lambda 表达式中的初始化操作,并缓存结果,之后的访问直接返回缓存的值。
Lazy
的核心是通过 LazyImpl
类来实现的。以下是 LazyImpl
类的简化版本,以便更好地理解它的工作原理:
// 简化版本伪代码
class XxxLazyImpl<out T>(initializer: () -> T) : Lazy<T> {
private var initializer: (() -> T)? = initializer
@Volatile
private var _value: Any? = UNINITIALIZED_VALUE
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
// 执行初始化操作
val typedValue = initializer!!()
_value = typedValue
initializer = null
return typedValue
}
}
下面是对这段代码的详细分析:
XxxLazyImpl
是一个泛型类,使用了一个泛型参数T
,表示属性的类型。initializer
是一个 lambda 表达式,用于指定属性的初始化逻辑。@Volatile private var _value: Any?
定义了一个_value
变量,使用@Volatile
标识,表示该变量是可见的并且可以在多线程环境下正确访问。_value
用于存储属性的值,初始值为UNINITIALIZED_VALUE
,表示属性尚未初始化。value
属性是Lazy
接口要求实现的属性,用于获取属性的值。
下面是 value
属性的工作原理:
- 首先,它检查
_value
是否等于UNINITIALIZED_VALUE
。如果不等于,说明属性已经初始化过了,直接将_value
强制类型转换为T
类型并返回。 - 如果
_value
等于UNINITIALIZED_VALUE
,表示属性尚未初始化,那么它执行initializer!!()
来执行属性的初始化逻辑,并将结果赋值给_value
。 - 接着,将
initializer
设置为null
,表示属性已经初始化,避免重复初始化。 - 最后,它通过强制类型转换将
_value
强制转换为T
类型,并返回该值。
这样,XxxLazyImpl
实现了属性的延迟初始化。当首次访问 value
属性时,会执行初始化操作,后续的访问将直接返回缓存的值,实现了属性的延迟初始化。
需要注意的是,这个简化的实现没有考虑线程安全性,因此在多线程环境下可能存在问题。实际的 Kotlin 标准库中的 Lazy
实现会考虑线程安全性,并提供不同的线程安全模式供选择。
2、Delegates
(1)observable 属性委托监视属性的变化
observable
属性委托允许你监视属性的变化,每当属性被修改时都会执行自定义的逻辑。以下是 observable
的基本语法和执行顺序:
基本语法:
var propertyName: T by Delegates.observable(initialValue) { property, oldValue, newValue ->
// 在这里执行属性变化时的操作
// 可以通知监听器,执行特定的操作等
}
propertyName
:需要委托的属性的名称。initialValue
:属性的初始值。observable
闭包:这个闭包会在属性值发生变化时被调用,它接受三个参数:property
:属性的元数据,通常在 lambda 中不需要使用。oldValue
:当前属性的旧值。newValue
:新设置的属性值。
执行顺序:
- 当首次访问属性时,属性的值会被初始化为
initialValue
。 - 当属性的值即将被修改时,
observable
闭包会被调用,你可以在这个闭包中执行特定的操作,例如通知监听器。 - 在
observable
闭包中,你可以访问属性的旧值(oldValue
)和新值(newValue
),并在属性变化时执行相应的操作。 observable
委托属性的修改不会被拦截,而是允许属性的变化。它主要用于在属性变化时通知其他部分代码,例如更新用户界面。
下面是一个示例,演示了如何使用 observable
属性委托:
var name: String by Delegates.observable("outa") { _, oldName, newName ->
println("Name changed from $oldName to $newName")
}
fun main() {
name = "danke"
name = "Katrina"
}
在上面的示例中,我们定义了一个 name
属性使用 observable
属性委托。observable
接受两个参数:
- 初始值(在这里是 “outa”)。
- 一个 lambda 表达式,用于定义属性变化时的逻辑。这个 lambda 接收三个参数:
property
:属性对象本身(在这里是name
)。oldValue
:修改前的旧值。newValue
:修改后的新值。
当 name
属性被修改时,observable
会执行 lambda 表达式内的逻辑,输出属性的变化情况。
在 main
函数中,我们修改了 name
属性的值,这导致了监视逻辑的执行。输出将是:
Name changed from outa to danke
Name changed from danke to Katrina
这样,就可以使用 observable
属性委托来监视属性的变化,以执行自定义的逻辑,这在许多应用中非常有用,例如在属性修改时更新 UI 或记录日志。
observable
属性委托和 Android Architecture Components 中的 ViewModel
有一些相似之处,但它们是用于不同的目的和场景的。
总结来说,observable
属性委托是一种通用的 Kotlin 特性,用于监视属性的变化,但在 Android 中需要额外的代码生成工具和手动管理属性变化的通知。LiveData
是专门用于 Android 的生命周期感知、自动通知和线程安全的数据观察机制,它更适合用于处理界面数据的变化和通知 UI 更新。通常情况下,建议在 Android 应用中使用 LiveData
来处理数据通知,而在普通 Kotlin 类中使用 observable
属性委托来监视属性的变化。
(2)vetoable 类似于 observable,但允许你阻止属性的修改
vetoable
是 Kotlin 标准库中的一个属性委托,它用于在属性值即将发生变化时执行自定义的验证或拦截操作。如果验证或拦截操作返回 false
,则属性的变化会被取消,新的值不会被接受。以下是 vetoable
的基本语法和执行顺序:
基本语法:
var propertyName: T by vetoable(initialValue) { property, oldValue, newValue ->
// 在这里执行验证或拦截操作
// 如果返回 true,新值将被接受;如果返回 false,新值将被取消
}
propertyName
:需要委托的属性的名称。initialValue
:属性的初始值。vetoable
闭包:这个闭包会在属性值即将发生变化时被调用,它接受三个参数:property
:属性的元数据,通常在 lambda 中不需要使用。oldValue
:当前属性的旧值。newValue
:即将设置的新值。
执行顺序:
- 当首次访问属性时,属性的值会被初始化为
initialValue
。 - 当属性的值即将被修改时,
vetoable
闭包会被调用,你可以在这个闭包中执行自定义的验证或拦截操作。 - 如果
vetoable
闭包返回true
,新值将被接受,属性值被修改。 - 如果
vetoable
闭包返回false
,新值将被取消,属性保持原来的值。
下面是一个示例,演示了如何使用 vetoable
属性委托来验证年龄是否在有效范围内:
var age: Int by Delegates.vetoable(0) {_, _, newAge ->
if (newAge >= 0 && newAge <= 120) {
true // 验证通过,接受新值
} else {
false // 验证失败,取消新值
}
}
fun main() {
println(age) // 输出 0,初始值
age = 130
println(age) // 输出 0,新值被取消
age = 18
println(age) // 输出 18,新值被接受
}
在上述示例中,age
属性使用 vetoable
委托,它会验证新值是否在有效范围内(0 到 120 岁)。如果新值不在范围内,验证失败,新值被取消;否则,验证通过,新值被接受。
vetoable
通常用于属性的验证,以确保属性值满足特定条件,如果不满足条件,可以取消属性的变化。例如,你可以使用 vetoable
来验证用户的输入是否有效,以防止无效的输入值被设置到属性中。
(3)notNull 确保属性在被访问之前已经被初始化
在 Kotlin 中,Delegates.notNull() 是一种属性委托,用于确保属性在被访问之前已经被初始化。这可以用于避免属性在未初始化的情况下引发空指针异常。
Delegates.notNull() 的基本语法如下:
var propertyName: Type by Delegates.notNull()
propertyName
是属性的名称。Type
是属性的类型。
使用 Delegates.notNull() 的属性必须是可变属性(var)。
var name : String by Delegates.notNull<String>()
fun main() {
println(name) // IllegalStateException: Property name should be initialized before get.
name = "danke"
println(name)
}
在上述示例中,name
属性使用 Delegates.notNull() 委托,但在访问该属性之前,必须初始化它。这样可以确保属性在被访问之前已经被赋值,避免了空指针异常。
Delegates.notNull() 适用于那些必须在使用前初始化的属性,它是一种有效的方式来确保属性不会在未初始化的情况下被访问。但请注意,它不是 Kotlin 推荐的最佳实践,通常情况下,更好的方式是在构造函数中初始化属性或者使用默认值来避免空指针异常。 Delegates.notNull() 应该被谨慎使用,因为它可能使代码更加复杂。
3、自定义属性委托
自定义属性委托是一种强大的 Kotlin 特性,允许你自定义属性的访问和修改行为。通过创建自定义属性委托,你可以在属性访问和修改时执行自定义的逻辑。
实现 ReadOnlyProperty
或 ReadWriteProperty
接口的方法,具体取决于属性是只读还是可读写。
下面是如何创建和使用自定义属性委托的示例:
// 创建一个自定义属性委托
class MyDelegate : ReadWriteProperty<Any?, String> {
private var storedValue: String = ""
// 获取属性值的逻辑
override fun getValue(thisRef: Any?, property: KProperty<*>): String {
return storedValue
}
// 设置属性值的逻辑
override fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
// 在设置属性值之前,可以执行自定义的逻辑
println("Setting value: $value")
storedValue = value
}
}
var delegate by MyDelegate()
fun main() {
// 访问属性会触发自定义的 getValue 逻辑
println(delegate) // 输出 ""
// 修改属性会触发自定义的 setValue 逻辑
delegate = "danke" // 输出 "Setting value: danke"
// 再次访问属性会触发自定义的 getValue 逻辑
println(delegate) // 输出 "danke"
}
在上述示例中,我们创建了一个名为 MyDelegate
的自定义属性委托,它实现了 ReadWriteProperty
接口,需要提供 getValue
和 setValue
方法。
getValue
方法用于获取属性值,你可以在其中定义自己的逻辑。setValue
方法用于设置属性值,你也可以在其中执行自定义的逻辑。
然后,我们使用 MyDelegate
委托 delegate
属性。当访问和修改 delegate
属性时,会触发自定义的 getValue
和 setValue
逻辑。
这种方式允许你在属性的访问和修改过程中执行自定义操作,例如执行验证、日志记录或其他业务逻辑。自定义属性委托是 Kotlin 中非常强大的特性,可以帮助你编写更具可维护性和可重用性的代码。
4、代理其他属性
在 Kotlin 中,双冒号 ::
是一种非常强大和灵活的语法,用于引用属性、函数或构造函数。它可以用于不同的上下文中,包括属性和方法的代理。双冒号 ::
引用函数,在之前的文章中已经介绍过了,我们现在来看一下用双冒号 ::
来引用代理。
下面是使用双冒号 :: 语法来代理属性的示例:
data class Person(var name: String)
fun main() {
val person = Person("danke")
var name: String by person::name
println("name: $name")
person.name = "蛋壳"
println("update name: $name")
}
在上述示例中,我们定义了一个 Person
数据类,其中包含一个可变属性 name
。然后,再创建了一个名为 person
的 Person
对象,并使用属性委托将 name
属性委托给了 person::name
,这意味着 name
属性的读取和写入操作会被委托给 person
对象的 name
属性。
我们打印一下输出:
name: danke
update name: 蛋壳
总结,属性委托允许你将属性的读取和写入操作委托给其他对象,这可以用于实现各种有趣的行为,例如在访问属性时执行自定义逻辑或者在属性值变化时触发其他操作。
5、代理Map
代理 Map 是 Kotlin 中一个非常强大的特性,它允许你通过委托属性将 Map 中的键值对映射到类的属性上。这在处理配置、动态数据、解析 JSON 等场景中非常有用。让我们看一个例子:
class Person(val map: MutableMap<String, Any?>) {
val name: String by map
val age: Int by map
}
fun main() {
val map: MutableMap<String, Any?> = mutableMapOf(
"name" to "danke",
"age" to 18
)
val person = Person(map)
println("Name: ${person.name}")
println("Age: ${person.age}")
// 修改映射中的值,会自动反映到属性上
map["name"] = "蛋壳"
map["age"] = 20
println("Name: ${person.name}")
println("Age: ${person.age}")
}
在上面的示例中,我们定义了一个 Person
类,该类接受一个 MutableMap
作为构造函数参数。然后,我们在 Person
类中定义了两个属性 name
和 age
,这些属性使用了 Map 作为委托,即通过 by map
实现。这意味着 Person
对象的 name
和 age
属性的值将从传入的 Map 中获取。
在 main
函数中,我们首先创建了一个 map
,其中包含了初始的键值对,然后创建了一个 person
对象,并将 map
传递给它。当我们修改了 map
中的值,这些变化也会自动反映到 person
对象的属性上。
这个示例展示了如何使用 Map 作为属性委托,将 Map 中的键值对映射到对象的属性上,从而使数据的访问变得非常方便。这在处理动态数据或配置时非常实用。
四、总结
最后我们总结一下 Kotlin 中的代理特点:
-
接口代理: Kotlin 提供了一种机制,使用
by
关键字可以将接口的实现委托给另一个对象。这有助于提高代码的清晰度和可读性。 -
by 关键字: 在代理模式中,
by
关键字用于指定代理对象,它必须返回一个具体的对象或表达式。 -
属性委托基础: Kotlin 中的属性委托是一种用于延迟初始化、监视属性变化等目的的机制。它通过
by
关键字将属性的实际操作委托给另一个对象。 -
代理模式仅支持接口类型: 在 Kotlin 中,代理模式主要用于接口类型。
-
Lazy 委托:
lazy
属性委托用于延迟初始化属性,只有在首次访问属性时才会执行初始化操作。它的初始化操作可以是一个闭包。 -
线程安全性: 在多线程环境下,需要考虑属性委托的线程安全性。Kotlin 提供了不同的线程安全模式,如
LazyThreadSafetyMode
,以满足不同需求。 -
Delegates 相关内容:
- Observable 委托:
observable
属性委托允许监视属性的变化,当属性的值发生变化时,可以触发额外的操作。 - Vetoable 委托:
vetoable
属性委托类似于observable
,但允许在属性修改前取消修改操作,可以用于属性值的校验和过滤。 - NotNull 委托:
notNull
属性委托确保属性在被访问之前已经被初始化,避免了空指针异常。
- Observable 委托:
-
自定义属性委托: Kotlin 允许开发者自定义属性委托,以满足特定需求。这可以通过实现
ReadWriteProperty
或ReadOnlyProperty
接口来实现。 -
代理其他属性: Kotlin 支持使用
by
关键字将一个属性的操作代理给另一个属性,这在代码重用和可读性方面非常有用。 -
代理 Map: Kotlin 内置支持将属性的操作代理给 Map 对象,这在动态操作属性和解析 JSON 数据时非常有用。