一周入门Kotlin(三)

来源:互联网 发布:中国家居建材 知乎 编辑:程序博客网 时间:2024/06/06 01:32

本章有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.InlineOnlypublic inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

2.如下图展示了他的子类情况:
这里写图片描述

  • SynchronizedLazyImpl:默认创建的对象 保证只有在某一线程中才能创建该对象
  • UnsafeLazyImpl:一个线程不安全的对象 多条线程调用可能会有问题
  • SafePublicationLazyImpl:线程安全对象,你可以创建在不同线程中多次创建 但是他只会返回第一次创建的实例

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

@kotlin.jvm.JvmVersionpublic 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的行为。

原创粉丝点击