美文网首页
每天学一点 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