User-Profile-Image
hankin
  • 5
  • Java
  • Kotlin
  • Spring
  • Web
  • SQL
  • MegaData
  • More
  • Experience
  • Enamiĝu al vi
  • 分类
    • Zuul
    • Zookeeper
    • XML
    • WebSocket
    • Web Notes
    • Web
    • Vue
    • Thymeleaf
    • SQL Server
    • SQL Notes
    • SQL
    • SpringSecurity
    • SpringMVC
    • SpringJPA
    • SpringCloud
    • SpringBoot
    • Spring Notes
    • Spring
    • Servlet
    • Ribbon
    • Redis
    • RabbitMQ
    • Python
    • PostgreSQL
    • OAuth2
    • NOSQL
    • Netty
    • MySQL
    • MyBatis
    • More
    • MinIO
    • MegaData
    • Maven
    • LoadBalancer
    • Kotlin Notes
    • Kotlin
    • Kafka
    • jQuery
    • JavaScript
    • Java Notes
    • Java
    • Hystrix
    • Git
    • Gateway
    • Freemarker
    • Feign
    • Eureka
    • ElasticSearch
    • Docker
    • Consul
    • Ajax
    • ActiveMQ
  • 页面
    • 归档
    • 摘要
    • 杂图
    • 问题随笔
  • 友链
    • Spring Cloud Alibaba
    • Spring Cloud Alibaba - 指南
    • Spring Cloud
    • Nacos
    • Docker
    • ElasticSearch
    • Kotlin中文版
    • Kotlin易百
    • KotlinWeb3
    • KotlinNhooo
    • 前端开源搜索
    • Ktorm ORM
    • Ktorm-KSP
    • Ebean ORM
    • Maven
    • 江南一点雨
    • 江南国际站
    • 设计模式
    • 熊猫大佬
    • java学习
    • kotlin函数查询
    • Istio 服务网格
    • istio
    • Ktor 异步 Web 框架
    • PostGis
    • kuangstudy
    • 源码地图
    • it教程吧
    • Arthas-JVM调优
    • Electron
    • bugstack虫洞栈
    • github大佬宝典
    • Sa-Token
    • 前端技术胖
    • bennyhuo-Kt大佬
    • Rickiyang博客
    • 李大辉大佬博客
    • KOIN
    • SQLDelight
    • Exposed-Kt-ORM
    • Javalin—Web 框架
    • http4k—HTTP包
    • 爱威尔大佬
    • 小土豆
    • 小胖哥安全框架
    • 负雪明烛刷题
    • Kotlin-FP-Arrow
    • Lua参考手册
    • 美团文章
    • Java 全栈知识体系
    • 尼恩架构师学习
    • 现代 JavaScript 教程
    • GO相关文档
    • Go学习导航
    • GoCN社区
    • GO极客兔兔-案例
    • 讯飞星火GPT
    • Hollis博客
    • PostgreSQL德哥
    • 优质博客推荐
    • 半兽人大佬
    • 系列教程
    • PostgreSQL文章
    • 云原生资料库
    • 并发博客大佬
Help?

Please contact us on our email for need any support

Support
    首页   ›   Kotlin   ›   Kotlin Notes   ›   正文
Kotlin Notes

Kotlin-类型进阶—代理(二十一)

2021-01-25 00:35:02
842  0 1
参考目录 隐藏
1) 接口代理
2) 属性代理(懒加载)
3) lazy
4) lateinit 只用于变量 var,而 lazy 只用于常量 val
5) 属性代理的基本定义
6) 常见属性代理基本使用
7) 1、Delegates.notNull()的基本使用
8) 2、Delegates.observable()的基本使用
9) 3、Delegates.vetoable()的基本使用
10) 代理属性分析
11) 1、Delegates.notNull()源码分析
12) 2、Delegates.observable()源码分析
13) 3、Delegates.vetoable()源码分析
14) 源码反编译分析
15) 自定义实例:

阅读完需:约 15 分钟

接口代理

interface Api {
    fun a()
    fun b()
    fun c()
}

class ApiImpl : Api {
    override fun a() {}
    override fun b() {}
    override fun c() {}
}

class ApiWrapper(val api: Api) : Api by api {
    override fun c() {
        println("c is called.")
        api.c()
    }
}

class kok : Api{
    override fun a() {
        println("akok")
    }

    override fun b() {
        println("bkok")
    }

    override fun c() {
        println("ckok")
    }

}

fun main() {
    ApiWrapper(kok()).c()
}

结果:

c is called.
ckok

接口代理其实就是可以把一些没必要实现的接口方法隐藏起来不去实现,方便一些,而不用每一个接口都要写一下。其中by关键字右边的就是实际代理类对象,它是构造函数中的一个属性,by关键字左边的是代理类对象实现的接口。

例子:

//超级数组 同时支持list和map接口,通过接口代理的方式不必实现list和map接口的所有方法
class SuperArray<E>(
    private val list: MutableList<E?> = ArrayList(),
    private val map: MutableMap<Any, E> = HashMap()
) : MutableList<E?> by list, MutableMap<Any, E> by map {

    override fun isEmpty() = list.isEmpty() && map.isEmpty()

    override val size: Int
        get() = list.size + map.size

    override fun clear() {
        list.clear()
        map.clear()
    }

    override operator fun set(index: Int, element: E?): E? {
        if (list.size <= index) {
            repeat(index - list.size + 1) {
                list.add(null)
            }
        }
        return list.set(index, element)
    }

    override fun toString(): String {
        return """List: [$list]; Map: [$map]"""
    }
}

fun main() {
    val superArray = SuperArray<String>()
    val superArray2 = SuperArray<String>()
    superArray += "Hello"
    superArray["Hello"] = "World"
    superArray2[superArray] = "World"

    superArray[1] = "world"
    superArray[4] = "!!!"

    println(superArray)
    println(superArray2)
}

属性代理(懒加载)

lazy

lazy属性代理 代理了Person实例的firstName的getter方法,实际是一个函数 传递一个lambda表达式

class Person(val name: String){
    //lazy属性代理 代理了Person实例的firstName的getter方法
    // 实际是一个函数 传递一个lambda表达式
    val firstName by lazy {
        name.split(" ")[0]
    }
    val lastName by lazy {
        name.split(" ")[1]
    }
}

lateinit 和 lazy 是 Kotlin 中的两种不同的延迟初始化的实现

lateinit 只用于变量 var,而 lazy 只用于常量 val

lazy 应用于单例模式(if-null-then-init-else-return),而且当且仅当变量被第一次调用的时候,委托方法才会执行。

lazy()是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main(args: Array<String>) {
    println(lazyValue)
    println(lazyValue)
}

打印结果
computed!
Hello

Hello

比如这样的常见操作,只获取,不赋值,并且多次使用的对象

    private val mUserMannager: UserMannager by lazy {
        UserMannager.getInstance()
    }

一般传统的进入界面就初始化所有的控件,而使用懒加载,只有用到时才会对控件初始化


属性代理的基本定义

属性代理是借助于代理设计模式,把这个模式应用于一个属性时,它可以将访问器的逻辑代理给一个辅助对象。

可以简单理解为属性的setter、getter访问器内部实现是交给一个代理对象来实现,相当于使用一个代理对象来替换了原来简单属性字段读写过程,而暴露外部属性操作还是不变的,照样是属性赋值和读取,只是setter、getter内部具体实现变了。

1、基本语法格式

class Student{
    var name: String by Delegate()
}

class Delegate{
    operator fun <T> getValue(thisRef: Any?, property: KProperty<*>): T{
        ...
    }
    operator fun <T> setValue(thisRef: Any?, property: KProperty<*>, value: T){
        ...
    }
}

属性name将它访问器的逻辑委托给了Delegate对象,通过by关键字对表达式Delegate()求值获取这个对象。任何符合属性代理规则都可以使用by关键字。属性代理类必须要遵循getValue(),setValue()方法约定,getValue、setValue方法可以是普通方法也可以是扩展方法,并且是方法是支持运算符重载。如果是val修饰的属性只需要具备getValue()方法即可。

属性代理基本流程就是代理类中的getValue()方法包含属性getter访问器的逻辑实现,setValue()方法包含了属性setter访问器的逻辑实现。当属性name执行赋值操作时,会触发属性setter访问器,然后在setter访问器内部调用delegate对象的setValue()方法;执行读取属性name操作时,会在getter访问器中调用delegate对象的getValue方法.

2、by关键字

by关键字实际上就是一个属性代理运算符重载的符号,任何一个具备属性代理规则的类,都可以使用by关键字对属性进行代理。

常见属性代理基本使用

属性代理是Kotlin独有的特性,我们自己去自定义属性代理,当然Kotlin还提供了几种常见的属性代理实现。例如:

  • Delegates.notNull()
  • Delegates.observable()
  • Delegates.vetoable()

1、Delegates.notNull()的基本使用

Delegate.notNull()代理主要用于可以不在构造器初始化时候初始化而是可以延迟到之后再初始化这个var修饰的属性,它和lateinit功能类似,但是也有一些不同,不过它们都需要注意的一点是属性的生命周期,开发者要做到可控,也就是一定要确保属性初始化是在属性使用之前,否则会抛出一个IllegalStateException.

package com.mikyou.kotlin.delegate

import kotlin.properties.Delegates

class Teacher {
    var name: String by Delegates.notNull()
}
fun main(args: Array<String>) {
    val teacher = Teacher().apply { name = "Mikyou" }
    println(teacher.name)
}

可能有的人并没有看到notNull()有什么大的用处,先说下大背景吧就会明白它的用处在哪了?

大背景: 在Kotlin开发中与Java不同的是在定义和声明属性时必须要做好初始化工作,否则编译器会提示报错的,不像Java只要定义就OK了,管你是否初始化呢。解释下这也是Kotlin优于Java地方之一,没错就是空类型安全,就是Kotlin在写代码时就让你明确一个属性是否初始化,不至于把这样的不明确定义抛到后面运行时。如果在Java你忘记了初始化,那么恭喜你在运行时你就会拿到空指针异常。

问题来了: 相比Java,Kotlin属性定义时多出了额外的属性初始化的工作。但是可能某个属性的值在开始定义的时候你并不知道,而是需要执行到后面的逻辑才能拿到。这时候解决方式大概有这么几种:

方式A: 开始初始化的时给属性赋值个默认值

方式B: 使用Delegates.notNull()属性代理

方式C: 使用lateinit修饰属性

以上三种方式有局限性。

  • 方式A就是很暴力直接赋默认值,对于基本类型还可以,但是对于引用类型的属性,赋值一个默认引用类型对象就感觉不太合适了。
  • 方式B适用于基本数据类型和引用类型,但是存在属性初始化必须在属性使用之前为前提条件。
  • 方式C仅仅适用于引用类型,但是也存在属性初始化必须在属性使用之前为前提条件。

2、Delegates.observable()的基本使用

Delegates.observable() 主要用于监控属性值发生变更,类似于一个观察者。当属性值被修改后会往外部抛出一个变更的回调。它需要传入两个参数,一个是initValue初始化的值,另一个就是回调lamba, 回调出property, oldValue, newValue三个参数。


package com.mikyou.kotlin.delegate

import kotlin.properties.Delegates

class Person{
    var address: String by Delegates.observable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
        println("property: ${property.name}  oldValue: $oldValue  newValue: $newValue")
    })
}

fun main(args: Array<String>) {
    val person = Person().apply { address = "ShangHai" }
    person.address = "BeiJing"
    person.address = "ShenZhen"
    person.address = "GuangZhou"
}

运行结果:

property: address  oldValue: NanJing  newValue: ShangHai
property: address  oldValue: ShangHai  newValue: BeiJing
property: address  oldValue: BeiJing  newValue: ShenZhen
property: address  oldValue: ShenZhen  newValue: GuangZhou

Process finished with exit code 0

3、Delegates.vetoable()的基本使用

Delegates.vetoable() 代理主要用于监控属性值发生变更,类似于一个观察者,当属性值被修改后会往外部抛出一个变更的回调。它需要传入两个参数,一个是initValue初始化的值,另一个就是回调lamba, 回调出property, oldValue, newValue三个参数。与observable不同的是这个回调会返回一个Boolean值,来决定此次属性值是否执行修改。

package com.mikyou.kotlin.delegate

import kotlin.properties.Delegates

class Person{
    var address: String by Delegates.vetoable(initialValue = "NanJing", onChange = {property, oldValue, newValue ->
        println("property: ${property.name}  oldValue: $oldValue  newValue: $newValue")
        return@vetoable newValue == "BeiJing"
    })
}

fun main(args: Array<String>) {
    val person = Person().apply { address = "NanJing" }
    person.address = "BeiJing"
    person.address = "ShangHai"
    person.address = "GuangZhou"
    println("address is ${person.address}")
}

代理属性分析

Delegates: 是一个代理单例对象,里面有notNull、observable、vetoable静态方法,每个方法返回不同的类型代理对象

NotNullVar: notNull方法返回代理对象的类

ObserableProperty: observable、vetoable方法返回代理对象的类

ReadOnlyProperty: 只读属性代理对象的通用接口

ReadWriteProperty: 读写属性代理对象的通用接口

1、Delegates.notNull()源码分析

notNull()首先是一个方法,返回的是一个NotNullVar属性代理实例;那么它处理核心逻辑就是NotNullVar内部的setValue和getValue方法,一起来瞅一眼。

public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

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

通过源码可以看到一旦getValue中的value是为null,那么就会抛出一个IllegalStateException,也就是在使用该属性之前没有做初始化。实际上可以理解在访问器getter加了一层判空的代理实现。

2、Delegates.observable()源码分析

observable()是一个方法,返回的是一个ObservableProperty属性代理实例;那它是怎么做到在属性值发生变化通知到外部的呢,其实很简单,首先在内部保留一个oldValue用于存储上一次的值,然后就在ObservableProperty类setValue方法执行真正赋值之后再向外部抛出了一个afterChange的回调,并且把oldValue,newValue,property回调到外部,最终利用onChange方法回调到最外层。

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        val oldValue = this.value
        if (!beforeChange(property, oldValue, value)) {
            return
        }
        this.value = value
        afterChange(property, oldValue, value)
    }
 public inline fun <T> observable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit):
        ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
            override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue)
 }

3、Delegates.vetoable()源码分析

vetoable()是一个方法,返回的是一个ObservableProperty属性代理实例;通过上面源码就可以发现,在setValue方法中执行真正赋值之前,会有一个判断逻辑,根据beforeChange回调方法返回的Boolean决定是否继续执行下面的真正赋值操作。如果beforChange()返回false就终止此次赋值,那么observable也不能得到回调,如果返回true则会继续此次赋值操作,并执行observable的回调。


源码反编译分析

class Teacher {
    var name: String by Delegates.notNull()
    var age: Int by Delegates.notNull()
}

实际上,以上那行代码是经历了两个步骤:

class Teacher {
    private val delegateString: ReadWriteProperty<Teacher, String> = Delegates.notNull()
    private val delegateInt: ReadWriteProperty<Teacher, Int> = Delegates.notNull()
    var name: String by delegateString
    var age: Int by delegateInt
}

Kotlin反编译后Java源码

public final class Teacher {
   // $FF: synthetic field
   //关键点一
   static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Teacher.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Teacher.class), "age", "getAge()I"))};
   //关键点二
   @NotNull
   private final ReadWriteProperty name$delegate;
   @NotNull
   private final ReadWriteProperty age$delegate;

   //关键点三
   @NotNull
   public final String getName() {
      return (String)this.name$delegate.getValue(this, $$delegatedProperties[0]);
   }

   public final void setName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
   }

   public final int getAge() {
      return ((Number)this.age$delegate.getValue(this, $$delegatedProperties[1])).intValue();
   }

   public final void setAge(int var1) {
      this.age$delegate.setValue(this, $$delegatedProperties[1], var1);
   }

   public Teacher() {
      this.name$delegate = Delegates.INSTANCE.notNull();
      this.age$delegate = Delegates.INSTANCE.notNull();
   }
}

分析过程:

  • 1、首先, Teacher类的name和age属性会自动生成对应的setter,getter方法,并且会自动生成对应的name\$delegate、age$delegate委托对象,如代码中标识的关键点二。
  • 2、然后,$$delegatedProperties的KProperty数组中会保存通过Kotlin反射出当前Teacher类中的中name,age属性,反射出来每个属性单独对应保存在KProperty数组中。
  • 2、然后,在对应属性setter,getter方法中是把具体的实现委托给对应的name\$delegate、age$delegate对象的setValue、getValue方法来实现的,如代码中标识的关键点三。
  • 3、最后在delegate对象中的setValue和getValue方法中的传入对应反射出来的属性以及相应的值。

自定义代理属性:

class Foo {
    val x: Int by X()
    var y: Int by X()
}

class X {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
        return 2
    }
    operator fun setValue(thisRef: Any?, property: KProperty<*>, i: Int) {

    }
}

其中getValue和setValue方法的参数写法是固定的

调用:

fun main() {
    val stateManager = StateManager()
    stateManager.state = 3
    stateManager.state = 4
    println(Foo().x)
}

自定义实例:

读取Config.properties中的配置项

Config.properties文件中一般是key-value的配置

author=xxxx
version=1.0
desc=This is a demo.
class PropertiesDelegate(private val path: String, private val defaultValue: String = ""){

    private lateinit var url: URL

    private val properties: Properties by lazy {
        val prop = Properties()
        url = try {
            javaClass.getResourceAsStream(path).use {
                prop.load(it)
            }
            javaClass.getResource(path)
        } catch (e: Exception) {
            try {
                ClassLoader.getSystemClassLoader().getResourceAsStream(path).use {
                    prop.load(it)
                }
                ClassLoader.getSystemClassLoader().getResource(path)!!
            } catch (e: Exception) {
                FileInputStream(path).use {
                    prop.load(it)
                }
                URL("file://${File(path).canonicalPath}")
            }
        }

        prop
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return properties.getProperty(property.name, defaultValue)
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        properties.setProperty(property.name, value)
        File(url.toURI()).outputStream().use {
            properties.store(it, "Hello!!")
        }
    }
}

abstract class AbsProperties(path: String){
    protected val prop = PropertiesDelegate(path)
}

class Config: AbsProperties("Config.properties"){
    var author by prop
    var version by prop
    var desc by prop
}

fun main() {
    val config = Config()
    println(config.author)
    config.author = "Bennyhuo"
    println(config.author)
}

其实主要还是实现getValue和setValue方法就可以,需要注意的是签名写法,setValue最后一个参数是设置的值的类型,而getValue的返回值就是对应获取的值的类型。

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
         
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        
    }

只要定义好了getValue和setValue方法,然后就可以通过by操作符去代理了

var 变量 by 【实现getValue和setValue的类】的实例()

如本文“对您有用”,欢迎随意打赏作者,让我们坚持创作!

1 打赏
Enamiĝu al vi
不要为明天忧虑.因为明天自有明天的忧虑.一天的难处一天当就够了。
543文章 68评论 294点赞 593692浏览

随机文章
Kotlin-内置类型—区间(四)
4年前
Git基操学习—3
5年前
MyBatis笔记8—typeHandlers
5年前
Kotlin-类型初步—智能类型转换(十)
4年前
SpringBoot—WebMvcConfigurer详解
5年前
博客统计
  • 日志总数:543 篇
  • 评论数目:68 条
  • 建站日期:2020-03-06
  • 运行天数:1927 天
  • 标签总数:23 个
  • 最后更新:2024-12-20
Copyright © 2025 网站备案号: 浙ICP备20017730号 身体没有灵魂是死的,信心没有行为也是死的。
主页
页面
  • 归档
  • 摘要
  • 杂图
  • 问题随笔
博主
Enamiĝu al vi
Enamiĝu al vi 管理员
To be, or not to be
543 文章 68 评论 593692 浏览
测试
测试
看板娘
赞赏作者

请通过微信、支付宝 APP 扫一扫

感谢您对作者的支持!

 支付宝 微信支付