属性声明
在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的功能
代理/委托属性
功能要求:
- **延迟属性:**只在第一次访问时计算他的值
- **可观察属性:**坚挺着从这获取这个属性更新的通知
- 在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
函数并接受如下参数
*
thisTef
与getValue()
*一样*
property
与getValue()
*一样新值——必须和属性类型一致或是它的父类
*getValue
和setValue
*函数必须作为代理类的成员函数或者扩展函数。扩展函数对与想要对对象代理原本没有的函数是十分有用。两种函数必须标记关键字operator
关键字
代理类可能实现ReadOnlyProperty
和ReadWriteProperty
中的一个兵要求带有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::prop
是KProperty
类型的反射对象,该对象描述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)
}