美文网首页
每天学一点 Kotlin -- 多彩的类:对象表达式

每天学一点 Kotlin -- 多彩的类:对象表达式

作者: 冯可乐同学 | 来源:发表于2021-11-24 14:07 被阅读0次

    ----《第一季Kotlin崛起:次世代Android开发 》学习笔记

    总目录:每天学一点 Kotlin ---- 目录
    上一篇:每天学一点 Kotlin -- 多彩的类:枚举类
    下一篇:每天学一点 Kotlin -- 多彩的类:委托1

    1. 前景提要

    1.1 有这样一个问题:怎么才能创建一个经过扩展后的类的对象呢?方法是:用一个类继承另一个类,在子类中增加需要扩展的属性或方法,最后创建一个子类的对象即可。如下代码所示:

    open class One(age: Int) {
        var age: Int = age
    }
    
    class Two : One(age = 10) {
        fun newAddFun() {
            println("我是子类新加的成员")
        }
    }
    
    fun testObject_1() {
        var two = Two()
        println("age = ${two.age}")
        two.newAddFun()
    }
    
    fun main(args: Array<String>) {
        testObject_1()
    }
    

    打印结果:

    age = 10
    我是子类新加的成员
    

    1.2 这种扩展的方法很常见,但是:如果还想扩展类,就又要一个新的继承类。或者是仅需要创建扩展类的一个对象就要新声明一个类,那就很不值得了。解决这个问题还有更好的方法,就是通过对象表达式实现。

    2. 对象表达式

    2.1 对象表达式:不需要显式地声明一个新的类,用对象表达式创建的对象可以视为匿名类的实例,而且该匿名类只能使用一次。

    2.2 对象表达式是用关键字 object 实现的。举个栗子:

    open class One(age: Int) {
        var age: Int = age
    }
    
    fun testObject_2() {
        var oneObj = object : One(20) {
            fun newAddFun2() {
                println("我是匿名类新加的成员")
            }
        }
    
        println("age = ${oneObj.age}")
        oneObj.newAddFun2()
    }
    
    fun main(args: Array<String>) {
        testObject_2()
    }
    

    打印结果:

    age = 20
    我是匿名类新加的成员
    

    2.3 总结:
    (1) 运行的结果和继承方式扩展类(1.1中)是相同的,所以对象表达式完全可以实现继承的效果(其实对象表达式的实质还是继承)。
    (2) 使用对象表达式时,基类仍然是需要 open 修饰符的,否则对象表达式的语句就会报错。
    (3) 这个写法,跟在 Android 中使用 Handler 的写法可以说是非常像了。

    2.4 从结果来看,对象表达式完全可以实现继承的效果。但是有个问题: 就是如果上面的对象表达式var oneObj = object : One(20){}放在 main() 函数外面,作为一个全局的变量时, oneNew 的属性可以正常访问,但是方法不能调用。

    2.5 既然对象表达式的实质还是继承,那么除了增加成员属性,同样能覆盖属性或方法了。举个栗子:

    fun testObject_3() {
        var newObjClass2 = object : ObjClass2(20), ObjInterface {
            override fun interfaceFun() {
                println("var newObjClass2 --- interfaceFun() start")
            }
    
            override var age: Int = 30
            override fun classFun() {
                println("var newObjClass2 --- classFun() -- age = $age")
            }
        }
    
        println("testObject_3() -- newObjClass2.age = ${newObjClass2.age}")
        newObjClass2.classFun()
        newObjClass2.interfaceFun()
    }
    
    fun main(args: Array<String>) {
        testObject_3()
    }
    

    打印结果:

    testObject_3() -- newObjClass2.age = 30
    var newObjClass2 --- classFun() -- age = 30
    var newObjClass2 --- start
    

    2.6 特别强调的是:用对象表达式继承一个父类时,如果父类有构造函数,则必须传递相同数量和数据类型的值给表达式。如果不需要任何外界传递的值,则运用对象表达式时可以不写其他的基类。举个栗子:

    fun testObject_4() {
        var newObjClass3 = object {
            var name: String = "newObjClass3--name"
            fun subClassFu() {
                println("newObjClass3 -- subClassFu() start")
            }
        }
    
        println("testObject_4() -- newObjClass3.name = ${newObjClass3.name}")
        newObjClass3.subClassFu()
    }
    
    fun main(args: Array<String>) {
        testObject_4()
    }
    

    打印结果:

    testObject_4() -- newObjClass3.name = newObjClass3--name
    newObjClass3 -- subClassFu() start
    

    2.7 object 的优点不单单是用作一个对象,也可以在 object 后添加一个名词作为一个变量声明,这个变量和类的声明很像,同样可以继承其他的类,举个栗子:

    open class ObjClass4(name: String, age: Int) {
        var age: Int = age
        open var name: String = name
        open fun classFunction() {
            println("method in class ObjClass4")
        }
    }
    
    object newObjClass4 : ObjClass4("newObjClass4-name", 20) {
        var myName: String = "添加新的属性"
        fun myFunction() {
            print("添加新的方法 \n")
        }
    
        override fun classFunction() {
            print("覆盖父类中的方法 \n")
        }
    }
    
    fun testObject_5() {
        println("可以直接通过名词访问newObjClass4: ${newObjClass4.myName}")
        newObjClass4.myFunction()
        newObjClass4.classFunction()
    }
    
    fun main(args: Array<String>) {
        testObject_5()
    }
    

    打印结果:

    可以直接通过名词访问newObjClass4: 添加新的属性
    添加新的方法 
    覆盖父类中的方法 
    

    2.8 通过对象表达式声明对象和类的声明的区别:
    (1) 在声明语句上,对象表达式声明的关键字是 object,object 之后跟着的是名称。而类的声明用 class 标记并且可以用 open,data 等关键字修饰,class 后是类名(不是对象名);
    (2) 在构造函数上,对象表达式声明没有构造函数。类可以有主构造函数和次构造函数;
    (3) 在访问上,通过对象表达式声明的名称可以直接访问对象的属性和方法。而类必须先初始化一个类的实例,再通过该实例访问类的成员。

    3. 伴生对象

    3.1 类可以使用关键字 inner 声明一个内部类,这样在需要使用它的时候可以通过外部类访问内部类的属性或方法。在 Kotlin 中也可以像内部类那样把对象声明放在类的内部。

    3.2 伴生对象:通过使用关键字 companion 标记类内部的对象声明,将对象声明在类的内部,那么这个声明的对象就称为伴生对象。所谓伴生,就是声明对象在外部类声明的同时也被声明好了。

    3.3 举个栗子:一个孩子出生后,就可以看到 ta 的手脚面容等,那么孩子的手就是伴随孩子出生来的。

    class ObjClassKid(name: String, age: Int){
        var name: String = name
        var age: Int = age
    
        fun kidAct(){
            println("Kid.class -- kidAct()")
        }
    
        companion object hand{
            var size: Int = 1
            fun action(){
                println("Kid.class -- hand.object -- action() ")
            }
        }
    }
    
    fun testObject_6(){
        val kid = ObjClassKid("kid", 1)
        println("testObject_6() -- kid.name = ${kid.name}, kid.age = ${kid.age}")
        kid.kidAct()
    
        // kid.hand // 调用不到,编译器报错
        println("Kid.hand.size = ${ObjClassKid.hand.size}")
        ObjClassKid.hand.action()
    }
    
    fun main(args: Array<String>) { 
        testObject_6()
    }
    

    打印结果:

    testObject_6() -- kid.name = kid, kid.age = 1
    Kid.class -- kidAct()
    Kid.hand.size = 1
    Kid.class -- hand.object -- action()  
    

    3.4 因为伴生对象的名称是可以忽略的,所以上面的代码也可以写成这样:

    class ObjClassKid1(name: String, age: Int) {
        var name: String = name
        var age: Int = age
    
        fun kidAct() {
            println("ObjClassKid1.class -- kidAct()")
        }
    
        companion object {  // 写起来是方便了,看代码却更费脑了,尤其是代码量很多后。
            var size: Int = 1
            fun action() {
                println("ObjClassKid1.class -- hand.object -- action() ")
            }
        }
    }
    
    fun testObject_7() {
        println("Kid.hand.size = ${ObjClassKid1.Companion.size}")
        ObjClassKid1.Companion.action()
    }
    
    fun main(args: Array<String>) {
        testObject_7()
    }
    

    打印结果还是一样的。

    3.5 对于上面的两种方式,有一点需要注意,如果不忽略对象名称,那么访问伴生对象的方式是“类名.伴生对象名称.方法”,如果忽略了伴生对象的名称,那么访问伴生对象的方式是 “类名.Companion.方法”,这两种方式是不能混用的,不然编译器会报错的。

    3.6 如果要避免因为混用而引起的报错,在 Kotlin 中,还能直接通过“类名.伴生对象方法”的方式访问伴生对象的方法,这样不管有没有忽略伴生对象的名称都不会出错。但是这样的话就要考虑一个问题:如果外部类和伴生对象有相同名称的方法呢?在 Kotlin 中,通过“类名.方法”的方式调用的伴生对象的方法,而想要调用类里面的方法,则就要声明类的对象,然后通过对象类调用了。举个栗子:

    class ObjClassKid2() {
        fun action() {
            println("ObjClassKid2.class -- action()")
        }
    
        companion object hand {  // 写起来是方便了,看代码却更费脑了,尤其是代码量很多后。
            var size: Int = 1
            fun action() {
                println("ObjClassKid2.class -- hand.object -- action() ")
            }
        }
    }
    
    fun testObject_8() {
        ObjClassKid2.action()
        ObjClassKid2.hand.action()
    
        var kid2 = ObjClassKid2()
        kid2.action()
    }
    
    fun main(args: Array<String>) {
        testObject_8()
    }
    

    打印结果:

    ObjClassKid2.class -- hand.object -- action() 
    ObjClassKid2.class -- hand.object -- action() 
    ObjClassKid2.class -- action()
    

    3.7 伴生对象也是可继承类和实现接口的。举个栗子:

    interface InterfaceHand {
        fun grow()
    }
    
    class ObjClassKid3(name: String, age: Int) {
        var name: String = name
        var age: Int = age
    
        fun kidAct() {
            println("Kid.class -- kidAct()")
        }
    
        companion object hand : InterfaceHand {
            var size: Int = 1
            fun action() {
                println("Kid.class -- hand.object -- action() ")
            }
    
            override fun grow() {
                println("Kid.class -- hand.object -- override method grow() in hands")
            }
        }
    }
    
    fun testObject_9() {
        println("Kid.hand.size = ${ObjClassKid3.hand.size}")
        ObjClassKid3.hand.action()
        ObjClassKid3.hand.grow()
    }
    
    fun main(args: Array<String>) {
        testObject_9()
    }
    

    打印结果:

    Kid.hand.size = 1
    Kid.class -- hand.object -- action() 
    Kid.class -- hand.object -- override method grow() in hands
    

    3.8 总结
    (1) 伴生对象是声明在类内部的对象,使用关键字 companion 标记,且一个类中只能拥有一个伴生对象;
    (2) 伴生对象可以继承类和接口并覆盖父类/接口中的方法;
    (3) 伴生对象所在的类被加载时,伴生对象就被初始化;
    (4) 可以通过“类名.伴生对象名.方法 ”、“ 类名.Companion.方法”或“类名.方法”访问伴生对象的方法。

    相关代码:https://gitee.com/fzq.com/test-demo

    相关文章

      网友评论

          本文标题:每天学一点 Kotlin -- 多彩的类:对象表达式

          本文链接:https://www.haomeiwen.com/subject/tdytaltx.html