阅读 73

Kotlin学习(四):类和对象

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相似,一样在类名后面补充形如,<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()
        }
复制代码
文章分类
Android
文章标签