美文网首页
Kotlin学习(四):类和对象

Kotlin学习(四):类和对象

作者: 往事一块六毛八 | 来源:发表于2020-03-14 11:06 被阅读0次

    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()
            }
    

    相关文章

      网友评论

          本文标题:Kotlin学习(四):类和对象

          本文链接:https://www.haomeiwen.com/subject/wcwmshtx.html