Kotlin学习日志(五)类与对象(下)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Kotlin学习日志(五)类与对象(下)

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"
        }
    }
}

运行效果如下:


老鹰的飞翔行为:


20200313162117854.png


凤凰的游泳行为:


20200313163025779.png


大雁的飞翔行为:


20200313163159436.png


企鹅的游泳行为:


2020031316333333.png


鸵鸟的飞翔行为:


20200313163411726.png


鹂鹃的奔跑行为


2020031316350525.png


通过一顿操作之后,总结出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()
        }
    }
}


因为嵌套类无法访问外部类的成员,所以其方法只能返回自身的信息,运行效果图如下:


20200313170900686.png


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


运行效果图:


20200313171914645.png

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等方法,然后看一下运行的效果

20200314144620642.png

20200314144646250.png


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


运行效果图如下:


小溪的长度


20200314150614735.png


瀑布的长度:


20200314150654814.png


山涧的长度:


20200314150732133.png


大河的长度:


2020031415075838.png

学习过程也是断断续续的,碎片时间,能写完这篇博客实属不易啊,希望能帮到您,山高水长,后会有期~

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
目录
打赏
0
0
0
0
26
分享
相关文章
|
3月前
|
.net 自定义日志类
在.NET中,创建自定义日志类有助于更好地管理日志信息。示例展示了如何创建、配置和使用日志记录功能,包括写入日志文件、设置日志级别、格式化消息等。注意事项涵盖时间戳、日志级别、JSON序列化、线程安全、日志格式、文件处理及示例使用。请根据需求调整代码。
68 13
|
4月前
|
Kotlin学习教程(七)
《Kotlin学习教程(七)》主要介绍了Lambda表达式,这是一种匿名函数,广泛用于简化代码。文章通过与Java 8 Lambda表达式的对比,展示了Kotlin中Lambda的基本语法、参数声明、函数体定义及如何作为参数传递。示例包括按钮事件处理和字符串比较,突出了Lambda表达式的简洁性和实用性。
60 4
Kotlin学习教程(三)
Kotlin学习教程(三)
30 4
|
5月前
|
Kotlin学习教程(二)
Kotlin学习教程(二)
63 4
Kotlin学习教程(一)
Kotlin学习教程(一)
72 4
|
5月前
|
Kotlin学习教程(六)
《Kotlin学习教程(六)》介绍了Kotlin中的注解、反射、扩展函数及属性等内容。注解用于添加元数据,反射支持运行时自省,扩展则允许为现有类添加新功能,无需修改原类。本文还详细解释了静态扩展的使用方法,展示了如何通过companion object定义静态部分,并对其进行扩展。
34 2
Kotlin学习教程(五)
《Kotlin学习教程(五)》介绍了Kotlin中的泛型、嵌套类、内部类、匿名内部类、枚举、密封类、异常处理、对象、单例、对象表达式、伴生对象、委托等高级特性。具体内容包括泛型的定义和类型擦除、嵌套类和内部类的区别、匿名内部类的创建、枚举类的使用、密封类的声明和用途、异常处理机制、对象和单例的实现、对象表达式的应用、伴生对象的作用以及类委托和属性委托的使用方法。通过这些内容,读者可以深入理解Kotlin的高级特性和设计模式。
52 1
JVM知识体系学习七:了解JVM常用命令行参数、GC日志详解、调优三大方面(JVM规划和预调优、优化JVM环境、JVM运行出现的各种问题)、Arthas
这篇文章全面介绍了JVM的命令行参数、GC日志分析以及性能调优的各个方面,包括监控工具使用和实际案例分析。
269 3
|
5月前
|
log日志学习
【10月更文挑战第9天】 python处理log打印模块log的使用和介绍
131 0
Android面试之5个Kotlin深度面试题:协程、密封类和高阶函数
本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点。文章详细解析了Kotlin中的协程、扩展函数、高阶函数、密封类及`inline`和`reified`关键字在Android开发中的应用,帮助读者更好地理解和使用这些特性。
109 1