----《第一季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.方法”或“类名.方法”访问伴生对象的方法。
网友评论