前言

接触kotlin语言也有几年时间了。日常开发工作中也推荐使用kotlin,但是对于一些kotlin语言语法的细节没有进行系统学习。碎片的知识点让工作中屡屡碰壁,前些天开始学习compose时候,意识到基础没有打好,最近也在分阶段的复习kotlin语言语法知识点。并统一输出。

类委托:使用“by”关键

关键字by解决的问题是:

有继承实现导致的脆弱性,基类被子类继承并对某些功能进行了扩展,这样子类就依赖了父类实现细节。随着不断迭代,父类中实现细节会随着迭代而被修改。早期对父类实现细节的假设也会失效,最终导致程序以不正确的行为而告终。所以kotin 设计初衷就识别这样的问题。将类默认访问修饰符定义为final ,不能继承。如果要对不能继承的类进行拓展,装饰设计模式提供现成的解决方案。

by关键字,底层的原理就是系统会帮我们自动生成一些​​装饰设计模式​​需要实现的模板代码。使类结构更清晰

上面是手动实现代理,二下面例子是使用by关键字,由系统生成装设模式的模板代码
/**
* creat time 2022/6/13 下午9:00
* author kuaidao
* email : kuaidao2022@gmail.com
* 装饰器模式:与被装饰类实现同样的接口,持有被装饰类对象。
*/
class DelegatingCollection<T>:Collection<T>{

val _list=ArrayList<T>()
override val size: Int
get() = _list.size

override fun contains(element: T): Boolean {
return _list.contains(element)
}

override fun containsAll(elements: Collection<T>): Boolean {
return _list.containsAll(elements)
}

override fun isEmpty(): Boolean {
return _list.isEmpty()
}

override fun iterator(): Iterator<T> {
return _list.iterator()
}

}
//可以针对代理接口,使用by关键字

class DelegateCollect( innerList:ArrayList<T> = arrayListOf<T>()):Collection<T> by innerList{

}

​​装饰器设计模式​​:
本质创建一个新类,实现与原始类一样的接口,并将原始类用一个字段缓存,具有一样行为的代码由原始类实现,在原始类行为代码调动前后可以添加一些其他的装饰代码。这样可以保持原始类api不变的情况下,对原始类进行一个功能扩展,即装饰类的作用

委托属性:可重用属性访问逻辑

委托属性:是将属性的访问器逻辑委托给一个辅助对象称委托),并默认提供属性变化监听接口,可以定义一个监听回调在属性更改时收到通知事件。

基本语法

class Foo{

val type:Type by Deletegate()
}

//规定委托对象必须包含setValue,getValue方法(对应var类型属性)
class Deletegate{

operator fun getValue()
operator fun setValue(type:Type)

}

fun main(){
val foo =Foo()
foo.type //等价于 deletegate.getValue()
foo.type = type类型值 //等价于 deltegate.setValue()
}

惰性初始化和 by lazy{ }

惰性初始化对一线开发也不会陌生,fragment 懒加载。对于资源获取比较耗时的操作,一般处理方式会进行延迟初始化。第一次用到才会初始化,之后再次用到会直接将已经初始化好了的对象引用或者资源返回。不再进行初始化

import java.util.*
/**
* creat time 2022/6/14 上午10:29
* author kuaidao
* email : kuaidao2022@gmail.com
* 惰性初始化
*/
class SupportProperty{

private val _resources:List<Resources>?=null
private val _resources2:List<Resources>?=null
private val _resources3:List<Resources>?=null

val resources:List<Resources>
get() {
if(_resources==null){
_resources=initResources()
}else{
_resources
}
}

}

class Resources{ }

fun main(args: Array<String>) {
val supportProperty=SupportProperty()
//首次访问会初始化资源
supportProperty.resources
//第二次访问就会使用第一次加载的资源
supportProperty.resources
}

这里使用了支持属性技术,一个_resource用来存储资源缓存,另一个用来获取资源,前一个_resources 可以为null,而后一个不会为null,通过代码展示可以看到里面仅仅对resource 做了惰性初始化处理,那么如果一个类中需要对多个资源访问全部进行惰性初始化,靠堆代码实现并不是好的方案。kotlin by lazy{} 帮我们提供了惰性初始化方案。

同样的代码,一行搞定了惰性初始化。

class SupportProperty{

private val _resources:List<Resources> by lazy {initResources()}
}

lazy的参数是一个lambda表达式,表达式返回的对象需要实现getValue,setValue才能这么用。默认情况下lazy是线程安全的。这里系统提供了三种线程安全模式设置的接口

public enum class LazyThreadSafetyMode {

/**
* Locks are used to ensure that only a single thread can initialize the [Lazy] instance.
*/
SYNCHRONIZED,

/**
* Initializer function can be called several times on concurrent access to uninitialized [Lazy] instance value,
* but only the first returned value will be used as the value of [Lazy] instance.
*/
PUBLICATION,

/**
* No locks are used to synchronize an access to the [Lazy] instance value; if the instance is accessed from multiple threads, its behavior is undefined.
*
* This mode should not be used unless the [Lazy] instance is guaranteed never to be initialized from more than one thread.
*/
NONE,
}

例如:

private  val _resources:List<Resources> by lazy(LazyThreadSafetyMode.NONE) {initResources()}

手动实现委托属性

如下代码,通过提供PropertyChangeSupport 属性变更通知监听接口,在属性被修改的时候,发送 firePropertyChange( )变更事件,设置一个 PropertyChangeListener()监听,就可以在属性修改后收到通知。

/**
* creat time 2022/6/13 下午10:10
* author kuaidao
* email : kuaidao2022@gmail.com
* 属性变化监听,需要对每一个可能变化的属性增加变化发送变化事件,并且bean需要继承属性变化监听接口
*/

open class propertyChangeAware{
val changeSupport=PropertyChangeSupport(this)


fun addChangeSupportListener(listener: PropertyChangeListener){
changeSupport.addPropertyChangeListener(listener)
}

fun removeSupportChangeListener(listener: PropertyChangeListener){
changeSupport.removePropertyChangeListener(listener)
}
}

class Persion(val name:String,age:Int):propertyChangeAware(){
var age:Int=age
set(value){
val oldValue=field
field=value
changeSupport.firePropertyChange("age",oldValue, field)
}
}

fun main(args: Array<String>) {
val p=Persion("kuangdao",18)
println(p.toString() +"真实值")
p.addChangeSupportListener(object : PropertyChangeListener {
override fun propertyChange(evt: PropertyChangeEvent) {
println("propter =${evt.propertyName} oldvalue=${evt
.oldValue} newValue=${evt.newValue}")
}
})
println("调整成100")
p.age=100
}

这里面只对age属性做了一个变更监听,如果需要对多个属性变动做监听,可以将通过代码抽取出来进行复用。

class ObservableProperty<T>(val name:String,val value:T,val changeSupport:PropertyChangeSupport){

fun getvalue():T=value
fun setValue(newValue){
val oldValue=value
value=newValue
changeSupport.firePropertyChange("age",oldValue, newValue)
}
}

}

如上而Kotin 的委托属性功能可以让你拜托这些样板代码。kotlin实现代码路径
common/kotlin/properties/ObservableProperty.kt 包中

public abstract class ObservableProperty<V>(initialValue: V) : ReadWriteProperty<Any?, V> {
private var value = initialValue

/**
* The callback which is called before a change to the property value is attempted.
* The value of the property hasn't been changed yet, when this callback is invoked.
* If the callback returns `true` the value of the property is being set to the new value,
* and if the callback returns `false` the new value is discarded and the property remains its old value.
*/
protected open fun beforeChange(property: KProperty<*>, oldValue: V, newValue: V): Boolean = true

/**
* The callback which is called after the change of the property is made. The value of the property
* has already been changed when this callback is invoked.
*/
protected open fun afterChange(property: KProperty<*>, oldValue: V, newValue: V): Unit {}

public override fun getValue(thisRef: Any?, property: KProperty<*>): V {
return value
}

public override fun setValue(thisRef: Any?, property: KProperty<*>, value: V) {
val oldValue = this.value
if (!beforeChange(property, oldValue, value)) {
return
}
this.value = value
afterChange(property, oldValue, value)
}
}

委托属性的变换规则

class Foo{

val type:Type by Deletegate()
}

Deletegate实例会被保存到一个隐藏的属性中,称为 ,编译器也将用一个Kproperty类型对象来代表这个属性的称为,编译器生成代码

public final class Foo {
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.property1(new PropertyReference1Impl(Foo.class, "type", "getType()I", 0))};
@NotNull
private final Deletegate type$delegate = new Deletegate();

public final int getType() {
return this.type$delegate.getValue(this, $$delegatedProperties[0]);
}
}

编译代码回调将对属性的访问直接转接到代理对象上面 type$delegate.getValue( this,属性)
这个机制可以方便让我们对属性的访问做一些操作,比方说自定义属性存储的位置(map,数据库),在访问该属性时做一些验证等。

在map中保存属性值
/**
* creat time 2022/6/14 上午11:23
* author kuaidao
* email : kuaidao2022@gmail.com
*/
class Person{

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

fun setAttribe(name:String,value:String){
_resource.put(name,value)
}

val name:String
get() {
return _resource["name"]!!
}

val company:String by _resource
}


fun main(args: Array<String>) {
val p=Person();
val data = mapOf( "name" to "kuaiao","company" to "codeReview")
for((key,value) in data){
p.setAttribe(key,value)
}
//println(p.name)
println(p.company)
}

属性对应着map中的key。

总结:

委托属性可以用来重用逻辑,这些逻辑控制如何存储、初始化、访问和修改属性值,lazy标准库函数提供一种实现惰性初始化属性的简单方法,Delegates.observable函数可以用来添加属性更改的观察者,委托属性可以使用任意map来作为属性委托,来灵活处理具有可变属性集的对象

​1.kotlin简单语法练习​