3.5 接口代理
通过接口固然完成了相应行为,但是鸟类这个家族非常庞大,如果每种鸟都实现Behavior接口,工作量是非常大的,其实鸟类的行为并不多,可以分类为飞禽、水禽、走禽三个行为类
下面是飞禽的行为类代码示例:
package com.llw.kotlinstart.custom_class class BehaviorFly : Behavior { override fun fly(): String { return "翱翔天空" } override fun swim(): String { return "落水凤凰不如鸡" } override fun run(): String { return "能飞干嘛还要走" } override var skiledSporte: String = "飞翔" }
下面是水禽
package com.llw.kotlinstart.custom_class class BehaviorSwim : Behavior { override fun fly(): String { return "看情况,大雁能展翅高飞,企鹅却欲飞还休" } override fun swim(): String { return "怡然戏水" } override fun run(): String { return "赶鸭子上树" } override var skiledSporte: String = "游泳" }
下面是走禽
package com.llw.kotlinstart.custom_class class BehaviorRun : Behavior { override fun fly(): String { return "飞不起来" } override fun swim(): String { return "望洋兴叹" } override fun run(): String { return super.run() } override var skiledSporte: String = "奔跑" }
然后定义一个引用代理类的野禽基类,通过关键字by表示接口将由入参中的代理类实现,野禽基类WildFowl代码如下:
package com.llw.kotlinstart.custom_class //只有接口才能使用关键字by进行代理操作 class WildFowl (name:String,sex:Int=MALE,behavior: Behavior):Poultry(name,sex),Behavior by behavior{ }
然后在Activity中使用
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.llw.kotlinstart.custom_class.* import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { var count: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { var fowl = when (count++ % 6) { //把代理类作为输入参数来创建实例 0 -> WildFowl("老鹰", Poultry.MALE, BehaviorFly()) //由于sex字段是个默认参数,因此可通过命名参数给behavior赋值 1 -> WildFowl("凤凰", behavior = BehaviorFly()) 2 -> WildFowl("大雁", Poultry.FEMALE, BehaviorSwim()) 3 -> WildFowl("企鹅", behavior = BehaviorSwim()) 4 -> WildFowl("鸵鸟", Poultry.MALE, BehaviorRun()) else -> WildFowl("鹂鹃", behavior = BehaviorRun()) } var action = when (count % 11) { in 0..3 -> fowl.fly() 4, 7, 10 -> fowl.swim() else -> fowl.run() } tv_result.text = "${fowl.name}: $action" } } }
运行效果如下:
老鹰的飞翔行为:
凤凰的游泳行为:
大雁的飞翔行为:
企鹅的游泳行为:
鸵鸟的飞翔行为:
鹂鹃的奔跑行为
通过一顿操作之后,总结出Kotlin的类继承与Java相比有所不同,主要体现在以下几点:
(1)Kotlin的类默认不可被继承,若需继承,则要添加open声明,而Java的类默认是允许被继承的,只有添加final声明才表示不能为继承。
(2)Kotlin除了常规的三个开放性修饰符public、protected、private外,另外增加了修饰符internal,表示只对本模块开放。
(3)Java的类继承关键字extends以及接口实现关键字implement在Kotlin中都被冒号所取代。
(4)Kotlin允许在接口内部实现某个方法,而Java接口的内部方法只能是抽象方法。
(5)Kotlin引入了接口代理(类代理)的概念,而Java不存在代理的写法。
四、特殊类
4.1 嵌套类
一个类可以在单独的代码文件中定义,也可以在另一个类内部定义,后一种情况叫作嵌套类,即A类嵌套在B类之中,听起来和Java的嵌套类是一样的,但其实有所差别,Java的嵌套类允许访问外部类的成员,而Kotlin的嵌套类不允许访问外部类的成员,强行访问则会报错。
下面是示例代码:
package com.llw.kotlinstart.custom_class class Tree(var treeName: String) { //在类内部再定义一个类,这个新类称作嵌套类 class Flower(var flowerName: String) { fun getName(): String { return "这是一朵$flowerName" //普通的嵌套类不能访问外部类的成员,如treeName //否则编译器会报错:" Unresolved reference: *** " //return "这是${treeName}上的一朵$flowerName" } } }
调用嵌套类时,得在嵌套类的类名前面添加外部类的类名,相当于把这个嵌套类作为外部类的静态对象使用,在Activity中调用代码如下:
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.llw.kotlinstart.custom_class.* import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { //使用嵌套类时,只能引用外部类的类名,不能调用外部类的构造函数 val peachBlossom = Tree.Flower("桃花") tv_result.text = peachBlossom.getName() } } }
因为嵌套类无法访问外部类的成员,所以其方法只能返回自身的信息,运行效果图如下:
4.2 内部类
Kotlin限制了嵌套类不能访问外部类的成员,那还有什么方法可以实现此功能呢?针对该问题,Kotlin另外增加了关键字inner表示内部,把inner加在嵌套类的class前面,然后嵌套类就变成了内部类,所以Kotlin的内部类就相当于Java的嵌套类,而Kotlin的嵌套类则是加了访问限制的内部类。还是在之前的嵌套类Tree中,加一个内部类Fruit ,示例代码如下,
package com.llw.kotlinstart.custom_class class Tree(var treeName: String) { //在类内部再定义一个类,这个新类称作嵌套类 class Flower(var flowerName: String) { fun getName(): String { return "这是一朵$flowerName" //普通的嵌套类不能访问外部类的成员,如treeName //否则编译器会报错:" Unresolved reference: *** " //return "这是${treeName}上的一朵$flowerName" } } //嵌套类加上inner前缀,就变成内部类 inner class Fruit(var fruitName:String){ fun getName():String{ //只有声明为内部类(添加了关键字inner,才能访问内外部类的成员) return "这是${treeName}长出来的$fruitName" } } }
然后在Activity中调用
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.llw.kotlinstart.custom_class.* import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) btn_test.setOnClickListener { //使用嵌套类时,只能引用外部类的类名,不能调用外部类的构造函数 val peach = Tree("桃树").Fruit("桃子") tv_result.text = peach.getName() } } }
运行效果图:
4.3 枚举类
Java有一种枚举类型,它采用关键字enum来表达,其内部定义了一系列名称,通过有意义的名字比0、1、2这些数字能够更有效地表达语义,下面是一个Java定义枚举类型的代码示例:
package com.llw.kotlinstart.custom_class; enum Season {SPRING, SUMMER, AUTUMN, WINTER}
再来看Kotlin的枚举类
package com.llw.kotlinstart.custom_class enum class SeasonType { SPRING, SUMMER, AUTUMN, WINTER }
虽然看上去只比Java的枚举类型多了一个class,但是Kotlin中枚举类内部的枚举变量除了可以直接拿来赋值之外,还可以通过枚举值的几个属性获得对应的信息,例如ordinal属性用于获取该枚举值的序号,name属性用于获取该枚举值的名称。枚举变量本质上还是该类的一个实例,所以如果枚举类存在构造函数,枚举变量也必须调用对应的构造函数,这样做的好处是,每一个枚举值不但携带唯一的名称,还可以拥有更加个性化的特征描述。下面创建一个枚举类来说明,代码如下:
package com.llw.kotlinstart.custom_class enum class SeasonName(val seasonName: String) { SPRING("春天"), SUMMER("夏天"), AUTUMN("秋天"), WINTER("冬天") }
然后在Activity中使用枚举类SeasonType和SeasonName:
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.llw.kotlinstart.custom_class.* import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var count: Int = 0 btn_test.setOnClickListener { if (count % 2 == 0) { //ordinal表示枚举类型的序号,name表示枚举类型的名称 tv_result.text = when (count++ % 4) { SeasonType.SPRING.ordinal -> SeasonType.SPRING.name SeasonType.SUMMER.ordinal -> SeasonType.SUMMER.name SeasonType.AUTUMN.ordinal -> SeasonType.AUTUMN.name SeasonType.WINTER.ordinal -> SeasonType.WINTER.name else -> "未知" } } else { tv_result.text = when(count++ % 4){ //使用自定义属性seasonName表示个性化的描述 SeasonName.SPRING.ordinal -> SeasonName.SPRING.seasonName SeasonName.SUMMER.ordinal -> SeasonName.SUMMER.seasonName SeasonName.AUTUMN.ordinal -> SeasonName.AUTUMN.seasonName SeasonName.WINTER.ordinal -> SeasonName.WINTER.seasonName else -> "未知" //枚举类的构造函数是给枚举类型使用的,外部不能直接调用枚举类的构造函数 } } } } }
4.4 密封类
为了解决枚举值判断的多余分支问题,Kotlin提出了“密封类”得概念,密封类就像是一种更加严格的枚举类,它内部有且仅有自身的实例对象,所以是一个有限的自身实例集合,或者说,密封类采用了嵌套类的手段,它的嵌套类全部由自身派生而来,定义密封类的时候,需要在该类的class前面加上关键字sealed作为标记。定义一个密封类,代码如下:
package com.llw.kotlinstart.custom_class sealed class SeasonSealed { //密封类内部的每个嵌套类都必须继承该类 class Spring (var name:String) : SeasonSealed() class Summer (var name:String) : SeasonSealed() class Autumn (var name:String) : SeasonSealed() class Winter (var name:String) : SeasonSealed() }
4.5 数据类
在Android实际开发中,我们经常需要定义一些实体类来存放返回的数据,在Java中一个数据类的通常我完成以下工作:
(1)定义实体类的每个字段,以及对字段进行初始赋值的构造函数。
(2)定义每个字段的get/set方法
(3)再判断两个数据对象是都相等时,通常每个字段都要比较一遍。
(4)在复制数据对象时,如果想另外修改某几个字段值,得再补充对应数量的赋值语句。
(5)在调试程序时,为获知数据对象里保存的字段值,得手工把每个字段值都打印出来。
这对于开发者来说无疑于一个繁琐的工作,而Kotlin鉴于此,推出了名为“数据类”这样的骚操作,其实说起来也比较简单,数据类的定义仅仅只要在class前面增加关键字data,并声明拥有完整输入参数的构造函数,即可无缝实现以下功能:
(1)自动声明与构造函数入参同名的属性字段。
(2)自动实现每个属性字段的get/set方法。
(3)自动提供equals方法,用于比较两个数据对象是否相等。
(4)自动提供copy方法,允许完整赋值某个数据对象,也可在复制后单独修改某几个字段的值。
(5)自动提供toString方法,用于打印数据对象中保存的所有字段值。
说的这么叼,也不知道是不是真的,来定义一个试一下吧。
代码如下:
package com.llw.kotlinstart.custom_class //数据类必须有主构造函数,且至少有一个输入参数 //并且要声明与输入参数同名的属性,即输入参数前面添加关键字val或者var //数据类不能是基类也不能是子类,不能是抽象类,也不能是内部类,更不能密封类 //我就是我,是颜色不一样的烟火 data class Plant( var name: String, var stem: String, var leaf: String, var flower: String, var fruit: String, var seed: String ) { }
然后在Activity中调用一下吧
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.llw.kotlinstart.custom_class.* import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var count: Int = 0 var lotus = Plant("莲","莲藕","莲叶","莲花","莲蓬","莲子") //数据类的copy方法不带参数,表示复制一模一样的的对象 var lotus2 = lotus.copy() btn_test.setOnClickListener { lotus2 = when(count++%2){ //copy方法带参数,表示指定参数另外赋值 0 -> lotus.copy(flower = "荷花") else -> lotus.copy(flower = "莲花") } //数据类自带equals方法,用于判断两个对象是否一样 var result = if(lotus2.equals(lotus)) "相等" else "不等" tv_result.text = "两个植物的比较结果是${result}\n"+ "第一个植物的描述是${lotus.toString()}\n"+ "第二个植物的描述是${lotus2.toString()}" } } }
上述代码调用了Plant对象的copy、equals、toString等方法,然后看一下运行的效果
4.6 模板类
模板类的应用相当广泛,Kotlin中保留了它,而且写法与Java类似,一样在类名后面补充形如“”或者“”这样的表达式,表示此处的参数类型待定,要等创建类实例时再确定具体的参数类型,举个例子,森林里有一条小河,小河的长度可能以数字形式输入(包括Int、Long、Float、Double),还有可能以字符串形式输入。针对于这个需求编写名为River的模板类,代码如下:
package com.llw.kotlinstart.custom_class //在类名后面添加"<T>",表示这是一个模板类 class River<T> (var name:String,var length:T) { fun getInfo():String{ var unit:String = when(length){ is String -> "米" //Int、Long、Float、Double都是数字类型Number is Number -> "m" else -> "" } return "${name}的长度是$length$unit" } }
然后在Activity中调用和这个模板类
package com.llw.kotlinstart import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.llw.kotlinstart.custom_class.* import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var count: Int = 0 btn_test.setOnClickListener { var river = when(count++%4){ //模板类(泛型类)声明对象时,要在模板类的类名后面加上"<参数类型>" 0 -> River<Int>("小溪",100) //如果编译器根据输入参数就能知晓参数类型,也可直接省略"<参数类型>" 1 -> River("瀑布",99.9f) 2 -> River<Double>("山涧",50.5) //如果你已经是老手了,那么怎么舒服怎么来,Kotlin的设计初衷就是偷懒 else -> River("大河","一千") } tv_result.text = river.getInfo() } } }
运行效果图如下:
小溪的长度
瀑布的长度:
山涧的长度:
大河的长度:
学习过程也是断断续续的,碎片时间,能写完这篇博客实属不易啊,希望能帮到您,山高水长,后会有期~