您的位置:首页 > 其它

一周入门Kotlin(三)

2017-05-28 11:04 387 查看
本章有2点主要内容,一个是关于属性的委托,二是关于对象接口和委托的问题探讨。

属性委托

1.现有代码的问题

例子1:下面的代码中,我创建了一个学生对象,并且希望不要一开始就为学生的姓名年龄和身高赋值,为了达到目的,我选择了将所有的属性置为null:

class Student {

var name: String? = null
var age: Int? = null
var height: Double? = null

fun test() {
Log.i("IT520", "SIMPLE TEST METHOD !")
}
}


于是,我在Activity中的onCreate()初始化了Student对象,并给他赋值了,代码如下:

class MainActivity : AppCompatActivity() {

var mStudent: Student?= null

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

mStudent=Student()
mStudent?.name="zhangsan"
mStudent?.age=18
mStudent?.height=1.70
}
}


为了使用mStudent的属性/方法,以后我每调用该对象,就应该多写一个问号,因为他有可能为null,这里仅仅是有可能。但作为程序员,我认为我已经初始化了,无需多此一举,那么问题就来了,能不能去掉这玩意(?)

例子2: 之前我做过电商的项目中有很多类里面出现了价格的问题,我们都知道价格为了方便,应该都是double类型,但是有的时候因为计算的问题,很容易造成某个对象的的价格为负数。为此,我希望每次为某个类重新设值的时候都不能为负数。

有的同学就说了,之前不是讲解了全局变量默认会自动生成setter/getter方法吗?我们可以重写setter/getter来达到我们的目的。

那么问题来了,如果有100个类里面都有一至两个价格的属性,我们是否要重写n次呢,是否有更加统一的做法来管理属性的内容?答案是有的

2.委托属性

1.上面第一个问题无非就是怕获取某个属性(间接调用getter方法)报空指针而已。

2.而上面第二个问题无非就是怕设置某个值到某个属性(间接调用setter方法)里面出现为负数的错误操作。

3.我们确实可以重写setter/getter,但是亲爱的乡亲们啊,一个项目的规模如此之大,是皇军也不会傻到一个个去重(抢)写(粮)吧(食),于是…

委托属性就这么产生了。大概的思想是这样,我们创建一个统一管理的类来处理我们要实现的业务逻辑,而在需要管理的属性上做个简单的标记,就能达到我们的想要的效果了。

这里提到三个问题:

1. 首先作为统一管理的类有什么特征?该类必须是ReadWriteProperty的子类。

2. 我们要管理什么东西呢?上面说到了,当然是属性的存(set)取(get )问题了

3. 如何将该类标记到某个属性上?在属性声明后面添加 by XXX()

接下来创建一个统一管理类,代码如下:

/**
* Created by lean on 2017/5/27.
*  T  委托属性的类型
*/
class Delegate<T> : ReadWriteProperty<Any?, T> {

var mValue: T? = null
/**
* @param thisRef 类的引用
* @param property 属性元数据
* */
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
Log.i("IT520", "getValue $thisRef -- ${property.name}")
return mValue!!
}

/**
* @param thisRef 类的引用
* @param property 被设置值的属性  .name获取属性名
* @param value 被设置的新值
* */
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
Log.i("IT520", "setValue $thisRef -- ${property.name} -- $value ")
mValue=value
}

}


接下来,我创建了一个类,并将管理类设置给某个属性,从这里可以看出 该对象不用做任何初始化:

class Example{
var p: String by Delegate()
}


在MainActivity中,我创建了该对象,并给p赋值后重新读取。当然肯定会调用p对象内部的setter()&getter()方法。除此之外,还会调用管理类的内部的setValue()&getValue()方法。

var example = Example()
example.p="Hello"
Log.i("IT520","log ${example.p} ")


这两方法可以监听哪个对象哪个属性传入什么值,取出什么值。在内部传值的时候我将数据缓存起来,等到取值的时候我再将数据放回去。如果我们有什么业务,都可以在这两方法中完成。完成后,哪些属性需要实现该规则的,都在后面添加 by Delegate()即可。

通过委托属性,问题1只需要在getValue()方法中实现如果当时属性值为null,就抛出空指针异常即可;而问题2只需要在setValue()方法中判断是否小于0即可,如果小于0则等于0

3.Lazy代理的使用

除了我们自己定义的委托属性,系统还开放了一些极为好用的委托属性,Lazy就是系统提供的一个单纯的get委托属性控制接口,比如下面的代码,我首先创建了一个MySqliteOpenHelper类用来实现数据库的创建,在类的构造器中,需要传入ctx上下文参数:

class MySqliteOpenHelper(val context: Context)
: SQLiteOpenHelper(context, DB_NEME, null, DB_VERSION) {

companion object{
public var DB_NEME ="xxx.db"
public var DB_VERSION =1
}

override fun onCreate(db: SQLiteDatabase?) {

}

override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {

}

}


在Application中,如果我想在onCreate()的时候才创建该dataBase对象,那么在声明dataBase对象的时候采用lazy属性委托,可以无需先创建该对象或者先将该对象置为null。

这段代码告诉系统,一开始dataBase是不会创建的,直到了onCreate()方法被调用,发现此刻需要dataBase这个对象,才调用了MySqliteOpenHelper(ctx:Context)单参构造器创建该对象。

class App: Application(){
//这里使用了lazy的单参构造器 将一个需要被延迟创建的对象的构造器作为参数传进来。
val dataBase :SQLiteOpenHelper by lazy {
MySqliteOpenHelper(applicationContext)
}

override fun onCreate() {
super.onCreate()
val db=dataBase.writableDatabase
}

}


4.Lazy代理源码剖析

val dataBase :SQLiteOpenHelper by lazy {
MySqliteOpenHelper(applicationContext)
}


1.首先这段代码会创建lazy对象。查看源码发现,lazy本身是一个接口,并在内部提供了value值(这里指代dataBase的属性值 而T代表MySqliteOpenHelper的类型)。

当获取某个被lazy装备的属性时,就会调用可扩展的getValue方法,并返回接口的value值。

public interface Lazy<out T> {
public val value: T
public fun isInitialized(): Boolean
}

@kotlin.internal.InlineOnly
public inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value


2.如下图展示了他的子类情况:



SynchronizedLazyImpl:默认创建的对象 保证只有在某一线程中才能创建该对象

UnsafeLazyImpl:一个线程不安全的对象 多条线程调用可能会有问题

SafePublicationLazyImpl:线程安全对象,你可以创建在不同线程中多次创建 但是他只会返回第一次创建的实例

3.回头发现我们刚刚调用的是lazy的单参构造器,内部创建了一个lazy的子类,默认就是线程安全的,并重写了value属性的get()方法,源码如下:

@kotlin.jvm.JvmVersion
public fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this

override val value: T
//当获取该对象的时候,就会调用get()
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}

return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
}
else {
//这里拿到开始的构造器 如果构造器有问题则报异常
val typedValue = initializer!!()
_value = typedValue
initializer = null
//默认返回值在最后一行 隐藏了return
typedValue
}
}
}

override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE

override fun toString(): String = if (isInitialized()) value.toString() else "Lazy value not initialized yet."

private fun writeReplace(): Any = InitializedLazyImpl(value)
}


5.系统提供的优秀委托属性

在kotlin的properties包下,有个Delegates类,其提供了几个优秀的 常见的委托属性:

public object Delegates {

//不允许为null
public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()

//检查传入的数据
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)
}

//决定传进来的数据是否保留
public inline fun <T> vetoable(initialValue: T, crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean):
ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) {
override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue)
}

}


Observable检查传入数据

不知道大家是否还记得我们之前谈到的问题2,现在,通过Observable对象,我们可以观察到内部的结果

如果你觉得下面的代码每次都要复制粘贴,可以将其封装到一个新类里:

class Product {

var id: Long = 0
//0.0 代表的是该属性一开始的默认值
var price: Double by Delegates.observable(0.0) {
//这里有3个参数property代表price属性 后2者代表其参数
property, old, newValue ->
if (newValue < 0)
Log.i("IT520","newValue lt 0")
else
Log.i("IT520","newValue gt 0")
}

}


vetoable决定是否保留某个数据

为了根据条件决定是否保留某个值,我们可以使用vetoable对象:

class Product {

var id: Long = 0
var price: Double by Delegates.vetoable(0.0) {

property, old, newValue ->
//只有大于等于0才能被保留
newValue >= 0.0
}

}


notNull定义某个不为null的属性

在开发的过程中,比如在MainActivity中,我们创建一个全局变量,但是希望他在onCreate()中才初始化,所以一开始就必须置为null,以后用到该属性,后面都必须添加”?”,特别麻烦,现在,我们可以使用notNull委托来解决该问题了。如下给出一个实例:

class Product {
var id: Long = 0
//这里定义了一个商品名 并告诉系统 该对象不为null
var name :String by Delegates.notNull()

}


该构造器内部的是怎么写的呢?

public fun <T: Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()

private class NotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
private var value: T? = null
//这里已经说明了 如果你一开始没给该值赋新的数据,就直接调用他的get() 则会抛出一个IllegalStateException
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
}
}


为了让Product里面的name能够一开始就定义,而在需要的时候才重新赋予新的值,我们可以定义自己的委托,只需修改系统noNull的setValue().

//定义自己的委托属性
private class SingleNotNullVar<T: Any>() : ReadWriteProperty<Any?, T> {
private var value: T? = null
//这里已经说明了 如果你一开始没给该值赋新的数据,就直接调用他的get() 则会抛出一个IllegalStateException
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) {
when (this.value?)
null -> this.value = value
else -> Unit
}
}


类的委托

1.Kotlin中的接口&实现接口的对象

kotlin的接口定义与java的类似,比如你想定义一个人类的共同特征,可以使用Person接口。

interface Person {
//定义了吃 玩的行为
fun eat()
fun play()
}


接下来我们让学生类和教师类实现该接口,并分别多实现了学习和教书的方法。当然,每个学生和老师也应该有自己的姓名,注意继承的代码中Person后面已经无需在写主构造器,也就是()了,因为他是接口不是类,代码如下:

class Student : Person {

var name: String = ""

override fun eat() {
Log.i("IT520", "$name 正在吃饭..")
}

override fun play() {
Log.i("IT520", "$name 正在玩..")
}

//定义学习的方法
fun study() {
Log.i("IT520", "$name 正在学习..")
}

}

class Teacher : Person {

var name: String = ""

override fun eat() {
Log.i("IT520", "$name 正在吃饭..")
}

override fun play() {
Log.i("IT520", "$name 正在玩..")
}

//定义学习的方法
fun study() {
Log.i("IT520", "$name 正在教书..")
}

}


换做是java类,我们发现相同的属性肯定是无法抽取的到接口中的,只能定义一个抽象的父类才能这么做。但是kotlin的接口是”无状态”,所以可以将属性的声明写到父类上,但却无法赋值。同时,接口还可以实现内部的函数,这点也是比较新颖的做法,Person接口的代码如下:

interface Person {

var name: String

//定义了吃 玩的行为
fun eat(){
Log.i("IT520", "$name 正在吃饭..")
}

fun play(){
Log.i("IT520", "$name 正在玩..")
}
}


作为子类,主需要实现自己的业务,有必要的情况下重写即可,接口的name已经声明了但是没赋值 所以这里必须重新定义,我们让构造器来声明该变量:

class Student(override var name: String) : Person {

//定义学习的方法
fun study() {
Log.i("IT520", "$name 正在学习..")
}

}

class Teacher(override var name: String) : Person {

//定义学习的方法
fun study() {
Log.i("IT520", "$name 正在教书..")
}

}


2.类委托的作用

上面我们已经讲解了属性委托的作用:它类似于AOP编程中对属性的传递的控制。

类的委托主要是用来声明一个类大部分主要功能.这里涉及到接口的2种用法:

* 如果想让自己的类本身实现某个组件的大部分功能,只需要实现接口即可(如上面的学生实现了Person接口,并默认实现了吃和玩的功能)

* 如果想让其他对象来来实现某个组件的大部分功能,就需要实现类的委托的(下面我们根据学生的例子来实现这个例子)。

//创建了类的代理 代理由外部传进来的参数决定
class Student(var p:Person) : Person by p{

//定义学习的方法
fun study() {
Log.i("IT520", "$name 正在学习..")
}

}


上面我们重新规划了学生类,希望在其他类中(如MainActivity)重新定义学生类的吃饭行为。那么在其他类中如何操作该实例呢?

var student = Student(object : Person {
//重新定义人物名称
override var name = "zhangsan"
//重写接口吃的行为
override fun eat() {
Log.i("IT520","小明 和 $name 一起去吃饭")
}

})
student.eat()


这里有必要告诉大家object这个关键字到底是什么意思,一般他代表的就是声明一个实例,对!你没看错。连对象我们都可以直接定义。而且我们的静态变量也使用了object关键字。如下我创建了Student对象,可以直接调用构造器,也可以这样:

object Student{
...
}


3.常见类委托都用在哪里

从上面的例子中我们可以看出,接口主要是抽取一个概念的大部分主要功能。

如果想让大部分功能自己实现,直接实现接口即可;如果想他类实现,用类委托即可。

举个例子:我们很多时候在同一APP中定义了不同样式但功能统一的ToolBar.这时,我们可以将ToolBar委托给他的使用者,让不同的页面对ToolBar做不同的处理。

再举个例子:如果一个Fragment在一个APP中频繁使用,你也可以抽取统一的功能。利用类的委托,在不同的Activity中控制该Fragment的行为。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: