Kotlin研发第十一弹——属性声明和代理

26 篇文章 0 订阅
25 篇文章 0 订阅

属性声明

在kotlin中类可以有属性,有两种

val: 关键字声明只读

**var:**声明可变属性

class Name{
var name:String ?=null
}

Getter和Setter方法

var声明后拥有getter和setter方法

val声明后只有getter方法

备用字段

在kotlin中类不可以有字段,然而当使用自定义的防蚊器时有时候需要备用字段,处于这些原因kotlin使用field关键字提供了自动备用字段

var counter = 0
set(value) {
if (value >= 0)
field = value
}

field关键字只能用于属性的访问器

编译器会检查访问器的代码,如果使用了备用字段(或者访问器时默认实现逻辑)就会自动生成备用字段,否则就不会

ps: 1.备用字段只能在字段构造器中声明
   2.备用字段必须拥有settet方法,也就是val字段不能声明备用字段

备用属性

备用属性和备用字段相似,不过备用属性就是去除备用字段而拥有备用的功能

private var _table: Map? = null
public val table: Map
get() {
if (_table.isNullOrEmpty())
_table = HashMap()
return _table ?: throw AssertionError("set to null by another thread")
}

备用属性就是使用备用功能,让_table一个private拥有public的功能

代理/委托属性

功能要求:

  1. **延迟属性:**只在第一次访问时计算他的值
  2. **可观察属性:**坚挺着从这获取这个属性更新的通知
  3. 在map总存储的属性,而不是每个属性创建一个字段
委托属性不需要实现任何的接口,但是要提供getValue()方法
(如果是var的话要提供setValue()方法),方法前加operator关键字。

事例:

class Preference<T>(val name: String, private val default: T) {
    companion object {
        private const val SP_NAME = "_base"

        private val instance: SharedPreferences by lazy {
            BaseApplication.context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
        }
        //删除全部数据
        fun clearPreference(){
            instance.edit().clear().apply()
        }
        //根据key删除sp中存储数据
        fun clearPreference(key:String){
            instance.edit().remove(key).apply()
        }
    }

  

    private fun getSharePreference(name: String, defaultValue: T): T = with(instance) {
        val result: Any = when (defaultValue) {
            is Long -> getLong(name, defaultValue)
            is Int -> getInt(name, defaultValue)
            is Float -> getFloat(name, defaultValue)
            is Boolean -> getBoolean(name, defaultValue)
            is String -> getString(name, defaultValue)
            else -> {
                getString(name, "")
            }
        }
        return result as T
    }

    private fun putSharePreference(name: String, defaultValue: T) = with(instance.edit()) {
        when (defaultValue) {
            is Long -> putLong(name, defaultValue)
            is Int -> putInt(name, defaultValue)
            is Float -> putFloat(name, defaultValue)
            is Boolean -> putBoolean(name, defaultValue)
            is String -> putString(name, defaultValue)
            else -> {
                putString(name, "")
            }
        }.apply()
    }

    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return getSharePreference(name, default)
    }

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

使用被代理的代码的方式

var userGoodsId:String by Preference("userGoodsId", "")

标准代理

Kotlin为几种常用的代理提供了工厂方法

1. 延迟:

lazy()是一个介绍lamdba并返回一个实现延迟属性的代理,第一次调用 get()执行lamdba并传递lazy()并存储结果,以后每次调用get()时只是简单返回之前存储的值

 val lazyValue:String by lazy{
  println("computed!")
  "LaZY_value"//默认的lazyValue值,并且纸杯初始化一次
 }
 fun main(args: Array<String>) {
      println(lazyValue)
     println(lazyValue)
 }

上面输出的结果为
 computed//第一次调用
 LaZY_value//第一次调用
 LaZY_value//第二次调用

默认情况下延迟属性的计算是同步的:该值得计算只在一个线程里,其他所有线程都将读取同样的值。

如果代理不需要同步初始化,而且允许出现多线程同时执行该操作,可以传LazyThreadSafetyMode.PUBLICATION参数给lazy()
如果你确信初始化只会在单线程中出现——不用考虑线程安全,那么可以使用LazyThreadSafetyMode.NONE该模式不会提供任何线程安全保障
如果想要使用线程安全,就使用blockingLazy():他还是按照同样的方式工作,但是保证了它的值只会在一个线程中计算,并且所有线程都获取同一个值
2. 可观察属性

Delegates.observable() 需要两个参数:一个初始值和一个用于修改的handler。每次我们给属性赋值时都会调用handler(在初始值操作后)。它有三个参数:一个将被复制的属性,旧值,新值:

class TestDelegate {
    //可观察你属性
    var name:String by Delegates.observable("<no name>"){
        property, oldValue, newValue -> println("$oldValue ->$newValue")
    }
}

打印结果

<no name> ->first
first ->second
如果你想能够打断复制并且取消它,用vetoable()代替observab()。传递vetable的handler会在赋新值之前调用
class TestDelegateVetoable{
    //可打断的观察属性
    var age:Int by Delegates.vetoable(15){
        property, oldValue, newValue -> println("$oldValue ->$newValue")
        newValue>20
    }
}

输出结果

15 ->16
15 ->25

两者不同:

**Delegates.observable():**赋值后值就保留到默认 initValue中
**Delegates. vetoable():**赋值后再次使用还是之前的initValue值

在Map中存储属性

把属性值存储在map中是一种常见的使用方式,这种操作经常出现在解析JSON或者其他动态的操作中。这种情况下你可以使用map来代理它的属性

class TestUserMulty(var map: MutableMap<String,Any?>){
    var name:String by map
    var age :Int by map
}

 val uset=TestUserMulty(map = mutableMapOf("name" to "wycMulty","age" to 28))
    uset.name="呵呵wyc"
    println(uset.name)
    println(uset.age)

输出结果

呵呵wyc
28

本地代理属性 ?不知道这样做的意义在哪里

我的理解:用于判断条件不满足的时候是否需要创建对象

你可以生命本地变量作为代理属性。比如你可以创建一个本地延迟变量;

fun testExample(uset:()->TestUserMulty){
    val user by lazy(uset)
    if(!user.name.isEmpty()){
        println(user.name)
        println(user.age)
    }
}
   
testExample { TestUserMulty(map = mutableMapOf("name" to "wycMulty","age" to 28)) }

user 只会在第一次访问时求值,如果if条件不满足,那么该变量体就不会执行节省了初始化时间和内存

属性代理的要求

**只读属性(val):**代理必须提供一个getValue的函数并接受如下参数:

*thisRef*接收者——必须和属性拥有者是同一种类型,或者是其父类
*property必须是KProperty<>或者它的父类

这个函数必须返回同样的类型或子类作为属性。

**可变属性(var):**代理必须添加setValue函数并接受如下参数

*thisTefgetValue()*一样

*propertygetValue()*一样

新值——必须和属性类型一致或是它的父类

*getValuesetValue*函数必须作为代理类的成员函数或者扩展函数。扩展函数对与想要对对象代理原本没有的函数是十分有用。两种函数必须标记关键字operator关键字

代理类可能实现ReadOnlyPropertyReadWriteProperty中的一个兵要求带有operator方法,这些接口再Kotlin标准库中有声明:

“interface ReadOnlyProperty<in R, out T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
}
interface ReadWriteProperty<in R, T> {
    operator fun getValue(thisRef: R, property: KProperty<*>): T
    operator fun setValue(thisRef: R, property: KProperty<*>, value: T)
}”

转换规则

在每个代理属性实现的背后,kotlin编译器都会生成辅助属性并代理给它。例如,对于属性prop,生成隐藏属性prop$delegate,而范文琦的代码只是简单的代理给这个附件属性

“class C {
    var prop: Type by MyDelegate()
}
// this code is generated by the compiler instead:
class C {
    private val prop$delegate = MyDelegate()
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}”

Kotlin 编译器在参数中提供了关于prop的所有必要信息:第一个参数
this引用到类C外部的实例,而this::propKProperty类型的反射对象,该对象描述prop自身。

“注意,直接在代码中引用绑定的可调用引用的语法 this::prop 自 Kotlin 1.1 起才可用”

提供代理(自1.1起)

通过定义provideDelegate操作符,可以扩展创建属性实现所代理对象的逻辑。如果by右侧所使用的对象讲provideDelegate定义为成员或扩展函数,那么会调用改函数来创建属性代理实例。

provideDelegate的一个可能的使用场景是在创建属性时检查属性一致性。
“class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // create delegate
    }
    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}
fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }
class MyUI {
    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}”


privatedelegate的参数与getValue相同:

  • thisRef——必须与属性所有者类型(对于扩展属性——指北扩展的类型)相同或者是他的超类
  • property——必须是扩展类型KProperty或其超类

在创建MyUI实例期间,为每个属性调用propvideDelegate方法,并立即执行必要的验证

class MyUI {
    val image by bindResource(ResourceID.image_id, "image")
    val text by bindResource(ResourceID.text_id, "text")
}
fun <T> MyUI.bindResource(
        id: ResourceID<T>,
        propertyName: String
): ReadOnlyProperty<MyUI, T> {
   checkProperty(this, propertyName)
   // create delegate

生成的代码中,provideDelegate方法用来初始化辅助prop$delegate属性的初始化。下面是属性声明val prop:Type by MyDelegate()生成的代码与上面(当provideDelegate方法不存在时)生成的代码的对比

class C {
    var prop: Type by MyDelegate()
}
// this code is generated by the compiler
// when the 'provideDelegate' function is available:
class C {
    // calling "provideDelegate" to create the additional "delegate" property
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    val prop: Type
        get() = prop$delegate.getValue(this, this::prop)

}

注意provideDelegate只影响辅助功能的现实不影响gettersetter
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值