kotlin中的委托,2024Android者未来的出路在哪里

println("age = ${p.age}")

}




打印结果如下:



age = -1

age = 50




[]( )使用属性委托来实现惰性初始化

-----------------------------------------------------------------------------



惰性初始化是一种常见的模式,比如在设计单例的时候可以设计为懒加载的单例,即单例的初始化是惰性初始化的,用到的时候才把单例实例化,没用到的时候为null。



示例:一个Person有emails属性,保存了此人的所有邮件,当我们不访问这个属性的时候,它为null,当我们访问它的时候,如果为null就进行加载emails的操作(耗时操作),加载完成后emails就不为空了,当再次访问emails属性时,它就不会再执行加载emails的操作了,因为之前已经加载好了,这就是惰性初始化,不需要的时候为null,需要的时候才初始化,而且只初始化一次。示例代码如下:



class Person(val name: String) {

private var _emails: List<Email>? = null



val emails: List<Email>

    get() {

        if (_emails == null) {

            _emails = loadEmails(this)

        }

        return _emails!!

    }

}




这里使用了所谓的**支持属性**技术:即一个属性,用两个字段来完成,一个是\_emails,用来存储这个属性的值,另一个是emails,用来提供对属性的读取。你需要两个属性来完成,因为他们具有不同的类型:\_emails可以为空,而emails为非空,这种技术经常会使用到,值得熟练掌握。



但这个代码有点繁瑣,如果需要多个这样的惰性属性,那这个类就很臃肿了。而且,它并不总是正常运行:这个实现不是线程安全的,Kotlin提供了更好的解决方案,使用委托属性会让代码变得简单得多,通过lazy函数来返回委托的属性,lazy是一个标准的库函数,示例如下:



class Person(val name: String) {

val emails by lazy{ loadEmails(this) }

}




lazy函数返回一个对象,该对象具有一个名为getValue且签名正确的方法,因此可以把它与by关键字一起使用来创建一个委托属性。lazy函数的参数是一个lambda,可以调用它来初始化这个值。默认情况下,lazy函数是线程安全的,如果需要,可以设置其他选项来告诉它要使用哪个锁,或者完全避开同步,如果该类永远不会在多线程环境中使用。lazy函数返回的Lazy实现类是SynchronizedLazyImpl,如下:



private class SynchronizedLazyImpl(initializer: () -> T, lock: Any? = null) : Lazy, Serializable {

private var initializer: (() -> T)? = initializer

private var _value: Any? = null



private val lock = lock ?: this



override val value: T

    get() {

        if (_value != null) {

            return _value as T

        }



        return synchronized(lock) {

            if (_value != null) {

                (_value as T)

            } else {

                _value = initializer!!()

                (_value as T)

            }

        }

    }

}




如上代码,我进行了一些简化与修改,以使其更容易理解,我们的loadEmails()函数其实就是一段lambda参数,会传给SynchronizedLazyImpl的initializer属性进行保存,当我们调用Person的emails属性时,其实调用的是getEmails()函数,而这个函数调用的是SynchronizedLazyImpl的getValue()函数,这个函数就会判断需要的值是否已经初始化,如果初始化了就直接返回,否则就进行初始化,而且这个操作是线程安全的。 原理还是比较简单的,就是把emails属性的getter委托给Lazy的getValue()函数,让我奇怪的是,这个getValue函数并没有符合Kotlin委托属性的约定啊,按道理getValue函数的应该大概是这样的:



fun getValue(person: Person, prop: KProperty<*>): List {

。。。

}




**到这里我们可以了解到by和by lazy的区别了,by用于属性委托,需要我们自己创建委托类,这个委托类必须要有getValue函数,如果是可写属性的话还需要有setValue函数,且方法签名要符合Kotlin的约定,如果这个属性是只读的,则我们可以使用lazy来帮我们创建一个委托类。这就是by 和by lazy的区别了,我们自己创建委托类也能实现属性的延迟加载,但是如果自己写,则每次都要写一堆模板代码,所以使用lazy来创建委托类代码就比较简洁了。**



[]( )属性委托的应用

----------------------------------------------------------------------



通过另一个例子来了解属性委托的应用:当一个对象的属性更改时通知监听器,在这许多不同的情况下都很有用,例如:当对象显示在UI时,你希望在对象变化时UI能自动刷新。Java具有用于此类通知的标准机制:PropertyChangeSupport和PropertyChangeEvent类。让我们看看在Kotlin中不使用委托属性的情况下,该如何使用它们,然后我们再将代码重构为用委托属性的方式。



PropertyChangeSupport类维护了一个监听器列表,并向它们发送PropertyChangeEvent事件。要使用它,你通常需要把这个类的一个实例存储为bean类的一个字段,并将属性更改的处理委托给它。在Android的类文档中有这样一个示例:



public class MyBean {

 private final PropertyChangeSupport pcs = new PropertyChangeSupport(this);



 public void addPropertyChangeListener(PropertyChangeListener listener) {

     this.pcs.addPropertyChangeListener(listener);

 }



 public void removePropertyChangeListener(PropertyChangeListener listener) {

     this.pcs.removePropertyChangeListener(listener);

 }



 private String value;



 public String getValue() {

     return this.value;

 }



 public void setValue(String newValue) {

     String oldValue = this.value;

     this.value = newValue;

     this.pcs.firePropertyChange("value", oldValue, newValue);

 }



 [...]

}




如果我们要很多个JavaBean,而且这些JavaBean的属性发生改变时,都想得到监听,为了避免要在每个JavaBean类中去添加一段重复代码,可以先创建一个基类,如下:



open class BaseBean {

protected val pcs = PropertyChangeSupport(this)



fun addPropertyChangeListener(listener: PropertyChangeListener) {

    pcs.addPropertyChangeListener(listener)

}



fun removePropertyChangeListener(listener: PropertyChangeListener) {

    pcs.removePropertyChangeListener(listener)

}

}




现在我们来写一个Person类,定义一个只读属性(作为一个人的名称,一般不会随时更改)和两个可写属性:年龄和工资,当这个人的年龄或工资发生变化 时,这个类将通知它的监听器。代码如下:



class Person(val name: String, age: Int, salary: Int) : BaseBean() {

var age: Int = age

    set(newValue) {

        val oldValue = field

        field = newValue

        pcs.firePropertyChange("age", oldValue, newValue)

    }



var salary: Int = salary

    set(newValue) {

        val oldValue = field

        field = newValue

        pcs.firePropertyChange("salary", oldValue, newValue)

    }

}

fun main() {

val p = Person("Dmitry", 34, 2000)

p.addPropertyChangeListener { event ->

    println("Property ${event.propertyName} changed from ${event.oldValue} to ${event.newValue}")

}

p.age = 35      // 输出:Property age changed from 34 to 35

p.salary = 2100 // 输出:Property salary changed from 2000 to 2100

}




setter中有很多重复的代码,我们把它提取到一个类,如下:



class ObservableProperty(

val propName: String,

var propValue: Int,

val changeSupport: PropertyChangeSupport

) {

fun getValue(): Int = propValue



fun setValue(newValue: Int) {

    val oldValue = propValue

    propValue = newValue

    changeSupport.firePropertyChange(propName, oldValue, newValue)

}

}

class Person(val name: String, age: Int, salary: Int) : PropertyChangeAware() {

val _age = ObservableProperty("age", age, changeSupport)

var age: Int

    get() = _age.getValue()

    set(newValue) = _age.setValue(newValue)



val _salary = ObservableProperty("salary", age, changeSupport)

var salary: Int

    get() = _salary.getValue()

    set(newValue) = _salary.setValue(newValue)

}

fun main() {

val p = Person("Dmitry", 34, 2000)

p.addPropertyChangeListener(

    PropertyChangeListener { event ->

        println("Property ${event.propertyName} changed from ${event.oldValue} to ${event.newValue}")

    }

)

p.age = 35      // 输出:Property age changed from 34 to 35

p.salary = 2100 // 输出:Property salary changed from 2000 to 2100

}




上面的示例代码不就是属性委托吗?如果能使用Kotlin的语法糖来完成就好了,但是在此之前,你需要更改ObservableProperty方法的签名,以符合Kotlin的约定方法。



class ObservableProperty(

var propValue: Int,

val changeSupport: PropertyChangeSupport

) {

operator fun getValue(p: Person, prop: KProperty<*>): Int = propValue



operator fun setValue(p: Person, prop: KProperty<*>, newValue: Int) {

    val oldValue = propValue

    propValue = newValue

    changeSupport.firePropertyChange(prop.name, oldValue, newValue)

}

}




与之前的版本相比,这次代码做了一些更改:



*   现在,按照约定的需要,getValue和setValue函数被标志了operator。

*   这些函数加了两个参数:一个用于接收属性的实倒(Person),用来设置或读取属性,另一个用于表示属性本身(KProperty)。

*   把name属性从主构造方法中删除了,因为现在可以通过KProperty访问属性名称了。



终于,你可以见识Kotlin委托属性的神奇了,如下:



class Person(val name: String, age: Int, salary: Int) : BaseBean() {

var age: Int by ObservableProperty(age, pcs)

var salary: Int by ObservableProperty(salary, pcs)

}




你不用手动去实现可观察的属性逻辑,可以使用Kotlin标准库,它已经包含了类似于ObservableProperty的类。标准库和这里使用的PropertyChangeSupport类没有耦合,因此你需要传递一个lambda,来告诉它如何通知属性值的更改。可以这样做:



class Person(val name: String, age: Int, salary: Int) : BaseBean() {

private val observer = { prop: KProperty<*>, oldValue: Int, newValue: Int ->

    pcs.firePropertyChange(prop.name, oldValue, newValue)

}

var age: Int by Delegates.observable(age, observer)

var salary: Int by Delegates.observable(salary, observer)

}




如果是这样的话,感觉BaseBean都不需要了,如下:



class Person(val name: String, age: Int, salary: Int, val onChange: ((property: KProperty<*>, oldValue: Int, newValue: Int) -> Unit)) : BaseBean() {

var age: Int by Delegates.observable(age, onChange)

var salary: Int by Delegates.observable(salary, onChange)

}




by右边的表达式不一定是构造函数直接创建对象,也可以是函数调用、另一个属性或任何其他表达式,只要这个表达式的值能够被编译器用正确的参数类型来调用getValue和setValue的对象。与其他约定一样,getValue和setValue可以是对象自己声明的方法或扩展函数。



[]( )委托属性的变换规则

------------------------------------------------------------------------



让我们来总结一下委托属性是怎么工作的,假设你已经有了一个具有委托属性的类:



class C {

var prop: Type by MyDelegate()

}

val c = C()




MyDelegate实例会被保存到一个隐藏的属性中,它被称为:<delegate>,编译器也将用一个KProperty类型的对象来代表这个属性,它被称为:<property>。编译器生成的代码如下:



class C {

private val <delegate> = MyDelegate()

var prop: Type

    get() = <delegate>.getValue(this, <property>)

    set(value: Type) = <delegate>.setValue(this, <property>, value)

}




因为,在每个属性访问器中,编译器都会生成对应的getValue和setValue方法,如下:



val c = C()

val x = c.prop // 编译为: val x = .getValue(c, )

c.prop = x // 编译为: .setValue(c, , x)




这个机制非常简单,但它可以实现许多有趣的场景。你可以自定义存储该属性值的位置(map、数据库表或者用户会话的Cookie中),以及在访问该属性时做点什么(比如添加验证、更改通知等)。所有这一切都可以用紧凑的代码完成。我们再来看看标准库中委托属性的另一个用法,然后看看如何在自己的框架中使用它们。



[]( )在map中保存属性值

-------------------------------------------------------------------------



委托属性发挥作用的另一种常见用法,是用在有动态定义的属性集的对象中。这样的对象有时被称为自订(expando)对象。例如,考虑一个联系人管理系统,可以用来存储有关联系人的任意信息。系统 中的每个人都有一些属性需要特殊处理(例如名字),以及每个人特有的数量任意的额外属性(例如,最小的孩子的生日)。



实现这种系统的一种方法是将人的所有属性存储在map中,不确定提供属性,来访问需要特殊处理的信息。来看个例子:



class Person {

private val _attributes = hashMapOf<String, String>()

fun setAttribute(attrName: String, value: String) {

    _attributes[attrName] = value

}

val name: String

    get() = _attributes["name"]!!

}

fun main() {

val p = Person()

val data = mapOf("name" to "Dmitry", "company" to "JetBrains")

for ((attrName, value) in data) {

    p.setAttribute(attrName, value)

}

println(p.name) // 输出:Dmitry

}




这里使用了一个通用的API来把数据加载到对象中(在实际项目中,可以是JSON反序列化或类似的方法),然后使用特定的API来访问一个属性的值。把它改为委托属性非常简单,可以直接将map放在by关键字后面,如下:



class Person {

private val _attributes = hashMapOf<String, String>()

fun setAttribute(attrName: String, value: String) {

    _attributes[attrName] = value

}

val name: String by _attributes

}




因为标准库已经在标准Map和MutableMap接口上定义了getValue和setValue扩展函数(定义在kotlin.collections.MapAccessors.kt文件中),所以这里可以直接这样用。属性的名称将自动用作在map中的键,属性值作为map中的值。



[]( )框架中的委托属性

-----------------------------------------------------------------------



更改存储和修改属性的方式对框架的开发人员非常有用。假设数据库中Users的表包含两列数据:字符串类型的name和整形的age。可以在Kotlin中定义Users和User类。在Kotlin代码中,所有存储在数据库中的用户实体的加载和更改都可以通过User类的实例来操作。



object Users : IdTable() {

val name = varchar("name", length = 50).index()

val age = integer("age")

}

class User(id: EntityID) : Entity(id) {

var name: String by Users.name

var age: Int by Users.age

}




Users对象描述数据库表的一个表:它被声明为一个对象,因为它对应整个表,所以只需要一个实例。对象的属性表示数据表的列。



User类的基类Entity,包含了实体的数据库列与值的映射。特定User的属性拥有这个用户在数据库中指定的值name和age。



框架用起来会特别方便,因为访问属性会自动从Entity类的映射中检索相应的值,而修改过的对象会被标记成脏数据,在需要时可将其保存到数据库中。可以在Kotlin代码中编写user.age += 1,数据库中的相应实体将自动更新。


**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**

**因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
![img](https://img-blog.csdnimg.cn/img_convert/7449a929ef9e80c8bdd07a9cee6b3c7f.png)
![img](https://img-blog.csdnimg.cn/img_convert/ead7303a998d080451fd8f67c47ca763.png)
![img](https://img-blog.csdnimg.cn/img_convert/fa190ffa7e41da8a70e3dc319e9e7880.png)
![img](https://img-blog.csdnimg.cn/img_convert/e84d77b406a2a8b328d4c8e6bb11dea4.png)
![img](https://img-blog.csdnimg.cn/img_convert/a3e94cf178a20eeb0df42e5113bc5e34.png)
![img](https://img-blog.csdnimg.cn/img_convert/9847efcadd64f0ab62fab035587b7d4e.png)
![img](https://img-blog.csdnimg.cn/13f2cb2e05a14868a3f0fd6ac81d625c.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)**
![img](https://img-blog.csdnimg.cn/img_convert/67a25589e62a974f192c8721749d30c5.png)



# 最后

上面这些公司都是时下最受欢迎的互联网大厂,他们的职级、薪资、福利也都讲的差不多了,相信大家都是有梦想和野心的人,心里多少应该都有些想法。

也相信很多人也都在为即将到来的金九银十做准备,也有不少人的目标都是这些公司。

我这边有不少朋友都在这些厂工作,其中也有很多人担任过面试官,**上面的资料也差不多都是从朋友那边打探来的。除了上面的信息,我这边还有这些大厂近年来的面试真题及解析,以及一些朋友出于兴趣和热爱一起整理的Android时下热门知识点的学习资料**。

**部分文件:**
![](https://img-blog.csdnimg.cn/img_convert/3641c9b4c251b5bcf9b5a8686b76d72d.webp?x-oss-process=image/format,png)
![](https://img-blog.csdnimg.cn/img_convert/eff3862824ad769180b01200e8003d92.webp?x-oss-process=image/format,png)
![](https://img-blog.csdnimg.cn/img_convert/f3aed6fcae1f7a7bcaea06a3fa494574.webp?x-oss-process=image/format,png)

[外链图片转存中...(img-ewUdgHu0-1711936340905)]
[外链图片转存中...(img-08O3C2Ky-1711936340905)]
![img](https://img-blog.csdnimg.cn/13f2cb2e05a14868a3f0fd6ac81d625c.png)

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

**由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

**如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)**
[外链图片转存中...(img-fogmpmT6-1711936340906)]



# 最后

上面这些公司都是时下最受欢迎的互联网大厂,他们的职级、薪资、福利也都讲的差不多了,相信大家都是有梦想和野心的人,心里多少应该都有些想法。

也相信很多人也都在为即将到来的金九银十做准备,也有不少人的目标都是这些公司。

我这边有不少朋友都在这些厂工作,其中也有很多人担任过面试官,**上面的资料也差不多都是从朋友那边打探来的。除了上面的信息,我这边还有这些大厂近年来的面试真题及解析,以及一些朋友出于兴趣和热爱一起整理的Android时下热门知识点的学习资料**。

**部分文件:**
[外链图片转存中...(img-fapvXSCl-1711936340906)]
[外链图片转存中...(img-MChF5WwD-1711936340906)]
[外链图片转存中...(img-sf94SBrF-1711936340906)]

> **本文已被[CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》]( )收录**
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值