5.1 类的构造
5.1.1 类的简单定义
先来看看在Android开发中多次见过的类MainActivity,在Java代码中 该类的写法如下所示:
//java 写法
public class MainActivity extends AppCompatActivity{
...
}
//kotlin写法
class MainActivity :AppCompatActivity(){
...
}
根据_上述代码简单地比较,Kotlin对类的写法与Java之间有以下几点区别:
(1) Kotlin省略 了关键字public,缘于它默认就是开放的。
(2) Kotlin用 冒号“:”代替extends,也就是通过冒号表示继承关系。
(3) Kotlin进行继承时,父类后面多了括号“()”。
接下来从简单的类的定义开始,定一个简单的动物类代码;
class Animal {
//类的初始
init {
println("Animal:这是个动物的类")
}
}
对应在外部为Animal类创建实例的代码如下所示:
btn_simple_class.setOnClickListener {
//var animal:Animal=Animal()
//因为根据等号后面的构造函数已经明确知道这个是Animal的实例,所以声明对象时可以不用指定它的类型
var animal = Animal()
}
- Kotlin对类进行初始化的函数名称是init,不像Java那样把类名作为构造函数的名称
- Kotlin对打印日志使用的类似C语音的println方法,而非Java的System.out.println
- Kotlin在创建实例时省略了关键字new
5.1.2 类的构造函数
第4章介绍函数的时候,提到Kotlin把函数看成是一种特殊的变量, 那么类在某种意义上算是一种特殊的函数。所以构造函数的输入参数得直接加到类名后面,而init方法仅仅表示创建类实例时的初始化动作。下面是添加了入参的类定义代码:
/**
* & 如果把函数看做一种特殊的变量,那么类在某种意义上也是一种特殊的也算是一种特殊的函数。
* & 构造函数的输入参数得直接加在类名后面
* & 针对多构造函数,Kotlin引入了主构造函数与二级构造函数的概念。
* & 跟在类名后面的参数是主构造函数的入参,同时init方法是主构造函数的内部代码
* & 二级构造函数,则可以在类内部直接书写完整的函数表达式
* & 二级构造函数从属于主构造函数,如果使用二级构造函数声明该类的实例,系统会先调用主构造函数的init代码,再调用二级构造函数的自身代码
*/
class AnimalMain constructor(context: Context, name: String) {
init {
Toast.makeText(context, "这是只$name", Toast.LENGTH_SHORT).show()
}
constructor(context: Context, name: String, sex: Int) : this(context, name) {
var sexName: String = if (sex == 0) "公" else "母"
Toast.makeText(context, "这只${name}是${sexName}", Toast.LENGTH_SHORT).show()
}
}
现在若想声明AnimalMain类的实例,既可通过主构造函数声明,也可通过二级构造函数声明,具体的声明代码如下所示:
var count = 0
btn_simple_class2.setOnClickListener {
when (count % 2) {
0 -> {
var animal = AnimalMain(this, "猫")
}
else -> {
var animal = AnimalMain(this, "猫", 0)
}
}
count++
}
二级构造函数的问题:若用二级构造函数声明的实例,会调用init方法的toast
为了解决二级构造函数调用主构造函数的痛点(toast方法执行两次),Kotlin设定了主构造函数不是必须的,也就是说类可以把几个构造函数都放在类内部定义,从而都变成了二级构造函数,如此就去掉了主构造函数
class AnimalSeparate {
constructor(context: Context, name: String) {
Toast.makeText(context, "这是只$name", Toast.LENGTH_SHORT).show()
}
constructor(context: Context, name: String, sex: Int) {
var sexName: String = if (sex == 0) "公" else "母"
Toast.makeText(context, "这只${name}是${sexName}", Toast.LENGTH_SHORT).show()
}
}
但是如此一来这样跟Java没什么区别
5.1.3 带默认参数的构造函数
读者是否还记得第4章介绍函数时说到的默认参数?类的构造函数同样也能添加默认参数。注意到AnimalSeparate类的两个构造函数只是相差一个输入 参数,所以完全可以把它们合并成一个带默认参数的主构造函数,新的主构造函数既能输入两个参数,又能输入三个参数。
/**
* &添加注解@JvmOverloads,则Java程序也能用,Kotlin也可以用
* & 添加了主业需要加上constructor
*/
class AnimalDefault(context: Context, name: String, sex: Int = 0) {
init {
println("8888")
var sexName: String = if (sex == 0) "公" else "母"
Toast.makeText(context, "这只${name}是${sexName}的", Toast.LENGTH_SHORT).show()
}
}
具体的外部调用代码如下所示:
btn_simple_class4.setOnClickListener {
when (count % 2) {
0 -> {
var animal = AnimalDefault(this, "猫")
}
else -> {
var animal = AnimalDefault(this, "猫", 1)
}
}
count++
}
kotlin 的类Java也可以用
/**
* &添加注解@JvmOverloads,则Java程序也能用,Kotlin也可以用
* & 添加了主业需要加上constructor
*/
class AnimalDefault @JvmOverloads constructor(context: Context, name: String, sex: Int = 0) {
init {
println("8888")
var sexName: String = if (sex == 0) "公" else "母"
Toast.makeText(context, "这只${name}是${sexName}的", Toast.LENGTH_SHORT).show()
}
}
5.2类的成员
5.2.1成员属性
既然属性字段和构造函数的入参存在一一 对应关系, 那么可以通过某种机制让编译器自动对其命名与赋值。Kotlin正 是遵循了类似的设计思路,且看下面的Kotlin代码是怎样实现的:
/**
* & 类的成员变量可以声明在构造函数中,前面加上关键字“var"或者”val",表示同时声明与该参数同名的可变(不可变)参数
* & 如果某个字段并非入参的同名属性,就需要在类内部显式声明该属性紫铜
*/
class WildAnimal(var name: String, val sex: Int = 0) {
}
不比不知道,比一比才发现原来Kotlin大幅精简了代码,包括:
(1)冗余的同名属性声明语句。
(2)冗余的同名属性赋值语句。
(3)冗余的属性获取方法与设置方法。
如果某个字段并非入参的同名属性,就需在类内部显式声明该属性字段。
class WildAnimal(var name: String, val sex: Int = 0) {
var sexName: String
/ /非空的成员属性必须在声明时赋值或者在构造函数中赋值
//否则编译器会报错"Property must be initialized or be abstract"
init {
sexName = if (sex == 0) "公" else "母"
}
}
使用:
btn_simple_class5.setOnClickListener {
var animal = when (count % 2) {
0 -> WildAnimal("猫")
else -> WildAnimal("猫", 1)
}
count++
btn_simple_class5.text = "这只${animal.name}是${animal.sexName}的"
}
5.2.2成员方法
类的成员除了成员属性还有成员方法,在类内部定义成员方法的过程类似于第4章提到的普通函数定义,具体参见“4.1函数的基本用法”和“4.2输入参数的变化”,这里不再赘述。
class WildAnimalFunction(var name: String, val sex: Int = 0) {
var sexName: String
init {
sexName = if (sex == 0) "公" else "母"
}
fun getDesc(tag: String): String {
return "欢迎来到$tag:这是只${name},是${sexName}的"
}
}
调用:
btn_simple_class6.setOnClickListener {
var animal = when (count % 2) {
0 -> WildAnimalFunction("猫")
else -> WildAnimalFunction("猫", 1)
}
count++
btn_simple_class6.text = animal.getDesc("动物园")
}
5.2.3 伴生对象
前面介绍了Kotlin对成员属性和成员方法的处理方式,外部无论是访问成员属性还是访问成员方法,都得先声明类的对象,再通过对象访问类的成员。可是Java还有静态成员.的概念,静态成员使用关键字static来修饰,且外部是通过“类名.静态成员名称”的形式访问静态成员(包括静态属性和静态方法)的。
然而Kotlin取消了关键字static,也就无法直接声明静态成员。为了弥补这方面的功能缺陷,Kotlin引入 了伴生对象的概念.
下面给出使用伴生对象扩充类定义的代码例子:
class WildAnimalCompanion(var name: String, val sex: Int = 0) {
var sexName: String
init {
sexName = if (sex == 0) "公" else "母"
}
fun getDesc(tag: String): String {
return "欢迎来到$tag:这只${name}是${sexName}的"
}
//在类加载的时候就运行伴生对象的代码块,其作用相当于Java里面的static{...}代码块
companion object WildAnimal {
fun judgeSex(sexName: String): Int {
var sex: Int = when (sexName) {
"公", "雄" -> 0
"母", "雌" -> 1
else -> -1
}
return sex
}
}
}
外部调用
val sexArray: Array<String> = arrayOf("公", "母", "雄", "雌")
btn_simple_class7.setOnClickListener {
var sexName: String = sexArray[count++ % 4]
//伴生对象的WildAnimal名称可以省略
// btn_simple_class7.text="${sexName} 对应的类型是${WildAnimalCompanion.WildAnimal.judgeSex(sexName)}"
btn_simple_class7.text = "$sexName 对应的类型是${WildAnimalCompanion.judgeSex(sexName)}"
}
5.2.4 静态属性
既然伴生对象能够实现静态函数,那么以此类推,同样也能实现静态属性,只要在伴生对象内部增加几个字段定义就行。
class WildAnimalConstant(var name: String, val sex: Int = MALE) {
var sexName: String
init {
sexName = if (sex == MALE) "公" else "母"
}
fun getDesc(tag: String): String {
return "欢迎来到$tag:这只${name}是${sexName}的"
}
companion object WildAnimal {
//静态常量的值是不可以改变的,所以要使用关键字val修饰
val MALE = 0
val FEMALE = 1
val UNKNOW = -1
fun judgeSex(sexName: String): Int {
var sex: Int = when (sexName) {
"公", "雄" -> MALE
"母", "雌" -> FEMALE
else -> -1
}
return sex
}
}
}
从以上代码看到,表示性别的数值0都被MALE代替,数值1被FEMALE代替,从而提高.了代码的可读性。外部若想进行动物性别判断,则可以使用表达式从以上代码看到,表示性别的数值0都被MALE代替,数值1被FEMALE代替,从而提高.
了代码的可读性。外部若想进行动物性别判断,则可以使用表达式
WildAnimalConstant.MALE表示雄性,使用WildAnimalConstant.FEMALE表示雌性。
5.3类的继承
5.3.1 开放性修饰符
Kotlin默认每个类都不能被继承(相当于Java类被finial修饰了)如果要让某个类成为基类,就需要把该类开放出来,即添加关键字open作为修饰符
有了基类框架,往里面补充成员属性跟成员方法,然后给这些成员添加开放性修饰符。就像Java一样,Kotlin也给出了4个开放性修饰符:
- public ->对所有人开放。kotlin的类,函数、变量不加开放性修饰的话默认就是public类型
- internal ->只对本模块内部开放,这是kotlin新增的关键字。对于app开发来说,本模块便是指app自身
- protected ->只对自己跟子类开放
- private ->只对自己开放,即私有
注意到这几个修饰符与open一样都加在类和函数前面,并且都包含“开放”的意思,乍看过去还真有点扑朔迷离,到底open跟这4个开放性修饰符是什么关系?其实很简单,open不控制某个对象的访问权限,只决定该对象能否繁衍开来,说白了,就是公告这个家伙有没有资格生儿育女。只有头戴open帽子的类,才允许作为基类派生出子类来;而头戴open帽子的函数,表示它允许在子类中进行重写。如果没戴上.open帽子,该类就只好打光棍了,无儿无女;函数没戴open帽子的话,类的孩子就没法修改它。
至于那4个开放性修饰符,则是用来限定允许访问某对象的外部范围,通俗地说,就是哪里的帅哥可以跟这个美女交朋友。头戴public的,表示全世界的帅哥都能跟她交朋友;头戴intermal的,表示只有本国的帅哥可以跟她交朋友;头戴protected的,表示只有本单位以及下属单位的帅哥可以跟她交朋友;头戴private的, 表示肥水不流外人田,只有本单位的帅哥才能跟这个美女交朋友。
注意:open 跟private 是对立的关系 不能同时出现
5.3.2普通类继承
/**
* 如果要让某个类成为基类,就需要把该类开放出来,即添加关键字open作为修饰符
*/
open class Bird(var name: String, val sex: Int = MALE) {
//变量,方法,类默认都是public,所以一般都是吧public省略掉了
// public var sexName:String
var sexName: String
init {
sexName = getSexName(sex)
}
open protected fun getSexName(sex: Int): String {
return if (sex == MALE) "公" else "母"
}
fun getDes(tag: String): String {
return "欢迎来到$tag:这只${name}是${sexName}的"
}
companion object BirdStatic {
val MALE = 0
val FEMALE = 1
val UNKONW = -1
fun judgeSex(sexName: String): Int {
var sex: Int = when (sexName) {
"公", "雄" -> MALE
"母", "雌" -> FEMALE
else -> UNKONW
}
return sex
}
}
}
声明一个子类
/**
* & 注意父类bird已经在构造函数声明了属性,故而子类Duck无须重复声明属性
* & 也就是说,子类的构造函数在输入参数前面不需要再加var和val
*/
class Duck(name: String = "鸭子", sex: Int = Bird.MALE) : Bird(name, sex) {
override public fun getSexName(sex: Int): String {
return if (sex == Bird.MALE) "是只公鸭子" else "是只母鸭子"
}
}
调用:
btn_simple_class8.setOnClickListener {
var sexBird = if (count++ % 3 == 0) Bird.MALE else Bird.FEMALE
var duck = Duck(sex = sexBird)
btn_simple_class8.text = duck.getDes("鸟语林") + "---" + duck.getSexName(1)
}
子类也可以定义新的成员属性和成员方法,或者重写被声明为open的父类方法。
/**
* 继承protected的方法,标准写法是“override protecte” --> override protecte fun getSexName(sex: Int): String {}
* 不过protected的方法继承过来默认是protected,所以也可直接省略protected -->override fun getSexName(sex: Int): String {}
* protected的方法继承之后允许将可见性升级为public,但不能降级为private --》 override public fun getSexName(sex: Int): String {}
*/
class Ostrich(name: String = "鸟", sex: Int = Bird.MALE) : Bird(name, sex) {
override public fun getSexName(sex: Int): String {
return if (sex == MALE) "雄" else "雌"
}
}
外部调用
btn_simple_class9.setOnClickListener {
var sexBird = if (count++ % 3 == 0) Bird.MALE else Bird.FEMALE
var ostrich = Ostrich(sex = sexBird)
btn_simple_class9.text = ostrich.getDes("鸟语林") + "---" + ostrich.getSexName(1)
}
5.3.3 抽象类
- 抽象类用abstract关键字修饰
- 因为抽象类不能直接使用,所以构造函数不必给默认参数赋值
- 抽象方法必须在子类进行重写,所以可以省略关键字open,因为abstract方法默认就是open类型
接下来定一个抽象类:
/**
* & 子类的构造函数,原来的输入参数不用加var和val,新增的输入参数必须加var或者val
* & 因为抽象类不能直接使用,所以构造函数不必给默认参数赋值
*/
abstract class Chicken(name: String, sex: Int, var voice: String) : Bird(name, sex) {
val numberArray: Array<String> = arrayOf("一", "二", "三", "四", "五", "六", "七", "八", "九", "十")
//抽象方法必须在子类进行重写,所以可以省略关键字open,因为abstract方法默认就是open类型
abstract fun callOut(times:Int):String
}
子类继承自父类
class Cock(name: String = "鸡", sex: Int = Bird.MALE, voice: String = "喔喔喔") : Chicken(name, sex, voice) {
override fun callOut(times: Int): String {
var count = when {
//when语句判断大于和小于时,要完整的判断条件写到每个分支中
times <= 0 -> 0
times >= 10 -> 9
else -> times
}
return "$sexName$name${voice}叫了${numberArray[count]}声,原来它在报晓啊"
}
}
class Hen(name: String = "鸡", sex: Int = Bird.MALE, voice: String = "咯咯咯") : Chicken(name, sex, voice) {
override fun callOut(times: Int): String {
var count = when {
//when语句判断大于和小于时,要完整的判断条件写到每个分支中
times <= 0 -> 0
times >= 10 -> 9
else -> times
}
return "$sexName$name${voice}叫了${numberArray[count]}声,原来它在下蛋啊"
}
}
外部调用:
btn_simple_class10.setOnClickListener {
btn_simple_class10.text = Cock().callOut(count++ % 10)
}
btn_simple_class11.setOnClickListener {
btn_simple_class11.text = Hen().callOut(count++ % 10)
}
5.3.4接口
- Kotlin的接口与Java的一样是为了间接实现多重继承
- 接口不能定义构造函数
- 接口的内部方法通常要被实现它的类进行重写
- 与Java不同的是,Kotlin允许在接口内部实现某个方法,而Java接口的所有内部方法都必须是抽象方法
定义一个接口如下:
interface Behavior {
//接口内部的方法默认是抽象的,所以不加abstract也可以,当然open也可以不加
open abstract fun fly(): String
fun swim(): String
//Kotlin的接口与Java的区别在于,kotlin接口的内部允许实现方法
//此时该方法不是抽象方法,就不能加上abstract
//不过该方法依然是open类型,接口内部的所有方法都默认是open类型
fun run(): String {
return "大多数鸟儿跑的并不像样,只有鸵鸟,鸸鹋等少数鸟类才擅长奔跑"
}
//Kotlin的接口允许声明抽象属性,实现该接口的类必须重载该属性
//与接口内部方法一样,抽象属性前面的open和abstract也可以省略
//open abstract var skilledSports:String
var skilledSports: String
}
那么其他类在实现Behavior接口时,跟类继承一样把接口名称放在冒号后面,也就是说,Java的extends和implement这两个关键字在Kotlin中都被冒号取代了。然后就像重写抽象类的抽象方法一样重写该接口的抽象方法.
下面定义一个类 重写接口方法
class Goose(name: String = "鹅", sex: Int = Bird.MALE) : Bird(name, sex), Behavior {
override fun fly(): String {
return "鹅能飞一点点,但飞不高,也飞不远。"
}
override fun swim(): String {
return "鹅鹅鹅,曲项向天歌。白毛浮绿水,红掌拨清波"
}
//因为接口已经实现了run方法,所以此处可以不实现该方法,当然你要实现它也行
override fun run(): String {
return super.run()
}
//重载了来自接口的抽象属性
override var skilledSports: String = "游泳"
}
外部调用:
btn_simple_class12.setOnClickListener {
btn_simple_class12.text = when (count++ % 3) {
0 -> Goose().fly()
1 -> Goose().swim()
else -> Goose().run()
}
}
5.3.5 接口代理
通过实现接口固然完成相应的行为,但是鸟类种类庞大,如果每个鸟都实现Behavior接口,可想而知工作量巨大。
因此, 按照习性我们将鸟类区分,主要分为:擅长飞翔的,擅长游泳的,擅长奔跑的,如果为每一种行为去写一个接口跟实现Behavior接口性质是一样的。
为了让鸟类适应不同场景的行为要求,Kotlin引入了接口代理的技术,即一个类先声明继承自某个接口,但并不直接实现该接口的方法,而是把已经实现该接口的代理类作为参数传给前面的类 ,相当于告诉前面的类:该接口我已经代替你实现了,你直接拿去用就好了
好处:输入的参数可以按照具体的业务场景传送相应的代理类,也就是说一只鸟该怎么飞,怎么游,怎么跑并不是一成不变的而是自有实际情况决定的。
/**
* 飞禽的行为
*/
class BehaviorFly : Behavior {
override fun fly(): String {
return "翱翔天空"
}
override fun swim(): String {
return "落水凤凰不如鸡"
}
override fun run(): String {
return "能飞干嘛走"
}
override var skilledSports: String = "飞翔"
}
/**
* 水禽的行为
*/
class BehaviorSwim : Behavior {
override fun fly(): String {
return "看情况,大雁能展翅高飞,企鹅却欲飞还休"
}
override fun swim(): String {
return "怡然戏水"
}
override fun run(): String {
return "赶鸭子上树"
}
override var skilledSports: String = "游泳"
}
/**
* 走禽的行为
*/
class BehaviorRun : Behavior {
override fun fly(): String {
return "飞不起来"
}
override fun swim(): String {
return "望洋兴叹"
}
override fun run(): String {
return super.run()
}
override var skilledSports: String = "奔跑"
}
接着定义一个引用了代理类的野禽基类,通过关键字by表示该接口将由入参中的代理类实现,野禽基类WildFowl的定义代码如下所示:
/**
* & 定义一个引用了代理类的野禽基类,通过关键字by表示该接口将由入参中的代理类实现
*/
class WildFowl(name:String,sex:Int=Bird.MALE,behavior:Behavior):Bird(name,sex),Behavior by behavior{
}
外部调用:
btn_simple_class13.setOnClickListener {
var fowl = when (count++ % 6) {
//把代理类作为输入参数来创建实例
0 -> WildFowl("老鹰", Bird.MALE, BehaviorFly())
//由于sex字段是个默认参数,因此可以通过命名参数给behavior赋值
1 -> WildFowl("凤凰", behavior = BehaviorFly())
2 -> WildFowl("大雁", Bird.FEMALE, BehaviorSwim())
3 -> WildFowl("企鹅", behavior = BehaviorSwim())
4 -> WildFowl("鸵鸟", Bird.MALE, BehaviorRun())
else -> WildFowl("鸸鹋", behavior = BehaviorRun())
}
var action = when (count % 11) {
in 0..3 -> fowl.fly()
4, 7, 10 -> fowl.swim()
else -> fowl.run()
}
btn_simple_class13.text = "${fowl.name}:$action"
}
总结一下,Kotlin的类继承与Java相比有所不同,主要体现在以下几点:
(1) Kotlin的类默认不可被继承,若需继承,则要添加open声明;而Java的类默认是允许被继承的,只有添加final声明才表示不能被继承。
(2) Kotlin除 了常规的三个开放性修饰符public、protected、 private外, 另外增加了修饰符internal,表示只对本模块开放。
(3) Java的类继承关键字extends以及接口实现关键字implement在Kotin中都被冒号所取代。
(4) Kotlin允许 在接口内部实现某个方法,而Java接口的内部方法只能是抽象方法。
(5) Kotlin引入了接口代理(类代理)的概念,而Java不存在代理的写法。
5.4几种特殊的类
5.4.1嵌套类
一个类可以在单独的代码文件中定义,也可以在另一个类内部定义,后一种情况叫作嵌套类,即A类嵌套在B类之中。
Java的嵌套类允许访问外部类的成员,而Kotlin的嵌套类不允许访问外部类的成员
调用嵌套类时,得在嵌套类的类名前添加外部类的类名
下面是定义一个嵌套类
class Tree(var treeName: String) {
//在类的内部再定一个类,这个新类称作嵌套类
//不能访问外部类成员,如treename
class Flower(var flowerName: String) {
fun getName(): String {
return "这是一朵${flowerName}"
}
}
}
调用嵌套类时,得在嵌套类的类名前面添加外部类的类名,相当于把这个嵌套类作为外部类的静态对象使用。嵌套类Flower的调用代码如下所示:
btn_simple_class14.setOnClickListener {
val peachBlossom = Tree.Flower("桃花")
btn_simple_class14.text = peachBlossom.getName()
}
5.4.2 内部类
既然Kotlin限制了嵌套类不能访问外部类的成员,那还有什么办法可以实现此功能呢?针对该问题,Kotin另外增加了关键字inner表示内部,把inner加在嵌套类的class前面,于是嵌套类华丽地转变为了内部类,这个内部类比起嵌套类的好处是能够访问外部类的成员。所以,Kotlin的内 部类就相当于Java的嵌套类,而Kotlin的 嵌套类则是加了访问限
class Tree(var treeName: String) {
//在类的内部再定一个类,这个新类称作嵌套类
//不能访问外部类成员,如treename
class Flower(var flowerName: String) {
fun getName(): String {
return "这是一朵${flowerName}"
}
}
//嵌套类加上inner前缀,就成为内部类
inner class Fruit(var fruitName: String) {
fun getName(): String {
return "这是${treeName}长出的$fruitName"
}
}
}
外部调用
btn_simple_class15.setOnClickListener {
val peach = Tree("桃树").Fruit("桃子")
btn_simple_class15.text = peach.getName()
}
5.4.3 枚举类
- Kotlin将Java的枚举类型改成枚举类
- 枚举类内部的变量除了可以直接拿来赋值之外,还可以通过枚举值的几个属性获得对应的信息
- 可以通过ordinal属性用于获取该枚举值的序号,name属性用户获取该枚举值的名称
- 枚举变量本质还是该类的一个实例,所以如果枚举类存在构造函数,枚举变量也必须调用对应的构造函数
enum class SeasonType(val seasonName: String) {
SPRING("春天"),
SUMMER("夏天"),
AUTUMN("秋天"),
WINTER("冬天")
}
外部调用:
btn_simple_class16.setOnClickListener {
if (count % 2 == 0) {
btn_simple_class16.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 {
btn_simple_class16.text = when (count++ % 4) {
SeasonType.SPRING.ordinal -> SeasonType.SPRING.seasonName
SeasonType.SUMMER.ordinal -> SeasonType.SUMMER.seasonName
SeasonType.AUTUMN.ordinal -> SeasonType.AUTUMN.seasonName
SeasonType.WINTER.ordinal -> SeasonType.WINTER.seasonName
else -> "未知"
}
}
}
5.4.4 密封类
5.4.3小节演示外部代码判断枚举值的时候,when语句末尾例行公事加了else分支。可是枚举类SeasonType内部一共有4个枚举 变量,照理when语句有4个分支就行了,最后的else分支纯粹是多此一举。出现此情况的缘故是,when语句不晓得SeasonType有4种枚举值,因此以防万一,必须要有else分支,除非编译器认为现有的几个分支已经足够。
为解决枚举值判断的多余分支问题(因为SeasonType只有四个类型,没必要加else判断),Kotlin提出了“密封类”的概念一种更加严格的枚举类,它的内部有且仅有自身的实例对象,所以是一个有限的自身实例集合 或者说,密封类采用了嵌套类的手段,它的嵌套类全部由自身派生而来,仿佛一个家谱,明确了长子,次子,三子,幺子
定义密封类的时候需要在该类的class前面加上关键字sealed作为标记
sealed class SeasonSealed {
//密封类的内部的每个嵌套类都必须继承该类
class Spring(var name: String) : SeasonSealed()
class Sunmer(var name: String) : SeasonSealed()
class Autumn(var name: String) : SeasonSealed()
class Winter(var name: String) : SeasonSealed()
}
外部调用:
btn_simple_class17.setOnClickListener {
var season = when (count++ % 4) {
0 -> SeasonSealed.Spring("春天")
1 -> SeasonSealed.Sunmer("夏天")
2 -> SeasonSealed.Autumn("秋天")
else -> SeasonSealed.Winter("冬天")
}
//密封类是一种严格的枚举类,它是一个有限的集合
//密封类确保条件分支覆盖了所有的枚举类型,因此不再需要else分支
btn_simple_class17.text = when (season) {
is SeasonSealed.Spring -> season.name
is SeasonSealed.Sunmer -> season.name
is SeasonSealed.Autumn -> season.name
is SeasonSealed.Winter -> season.name
}
}
5.4.5 数据类(javaBean类)
- 定义在class前面增加关键字“data",并声明拥有完整输入参数的构造函数,并可实现以下功能
- 自动声明与构造函数入参同名的属性字段
- 自动实现每个属性字段的get/set方法
- 自动提供equals方法,用于比较连个数据对象是否相等
- 自动提供copy方法,允许完整复制某个数据对象,也可以复制后单独修改某几个字段的值
- 允许提供toString方法,用于打印数据对象中保存的所有字段值
- 数据类必须有主构造函数,且至少有一个输入参数,因为它的属性字段要跟输入参数一一对应,如果没有属性字段,这个数据类保存不了数据,也就失去存在的意义
- 主构造函数的输入参数前面必须加关键字val后者var,这保证每个入参都会自动声明同名的属性字段
- 数据类有自己的一套行事规则,所以它只能是个独立的类,不能是其他类型的类,否则不同规则之间会产生冲突
data class Plant(var name:String,var stem:String,var leaf:String,var flower:String,var fruit:String,var seed:String){
}
外部调用:
var lotus = Plant("莲", "莲藕", "莲叶", "莲花", "莲蓬", "莲子")
//数据类的copy方法不带参数,表示复制一模一样的对象
var lotus2 = lotus.copy()
btn_simple_class18.setOnClickListener {
lotus2 = when (count++ % 2) {
//copy 方法带参数,表示指定参数另外赋值
0 -> lotus.copy(flower = "荷花")
else -> lotus.copy(flower = "莲花")
}
//数据类自带equals方法,用于判断两个对象是否一样
var result = if (lotus2.equals(lotus)) "相等" else "不等"
btn_simple_class18.text =
"两个植物的比较结果是${result}\n第一个植物的描述是${lotus.toString()}\n第二个植物的描述是${lotus2.toString()}\n"
}
5.4.6 模板类
- 即泛型类
- 定义格式跟Java相似,一样在类名后面补充形如<T>,<A,B>这样的表达式
- 外部调用模板类构造函数的时候,要在类名后面补充“<参数类型>”,从而动态指定实际的参数类型
class River<T>(var name: String, var length: T) {
fun getInfo(): String {
var unit: String = when (length) {
is String -> "米"
is Number -> "m"
else -> ""
}
return "${name}的长度是$length$unit"
}
}
外部调用:
btn_simple_class19.setOnClickListener {
var river = when (count++ % 4) {
//泛型类声明对象时,要在模板类的后面加上“<参数类型>”
0 -> River<Int>("小溪", 100)
//如果编译器根据输入参数就能知晓参数类型,也可以直接省略“<参数类型>"
1 -> River("瀑布", 99.9f)
1 -> River<Double>("瀑布", 55.5)
else -> River("大河", "一千")
}
btn_simple_class19.text = river.getInfo()
}