Kotlin开发笔记:类层次结构和继承

news2025/1/12 3:58:44

Kotlin开发笔记:类层次结构和继承

在这里插入图片描述

简介

面向对象编程的语言中,关于对象层次的抽象是很重要的,这会涉及到类层次的结构,接口和继承等内容,本篇文章将会介绍与之相关的内容,包括:

    1. 接口的相关内容
    1. 抽象类的相关内容
    1. 嵌套类和内部类
    1. 继承
    1. Sealed类
    1. 枚举类型的相关内容

接口

Kotlin中的接口特性

Kotlin中的接口和Java中的接口在语义上很类似,都是用来实现契约式设计的工具。接口作为规范,类作为这些契约式的实现者。但是在语法上又和Java有很大不同,具体来说,Kotlin中的接口更接近于类,限制更少。比如说,我们可以在Kotlin中的接口中实现方法而不需要使用default关键字,而且我们可以在接口中创建伴生对象,所以我们可以在接口中实现静态方法或者创建静态变量。

创建接口

Kotlin中创建接口和Java也非常类似,都是使用interface关键字,比如我们可以创建一个名为Remote的接口:

interface Remote{
    fun up()
    fun down()
    fun doubleup(){
        up()
        up()
    }
 }

该接口中包含两个抽象方法和一个实现了的方法,这个实现了的方法又依赖于抽象方法。但是仔细一想,我们实现了抽象方法,这个实现了的方法也自然实现了。

接下来,我们将伴生对象运用到接口中:

interface Remote{
    fun up()
    fun down()
    fun doubleup(){
        up()
        up()
    }

    companion object{
        val id:Int = 0

        fun combine(first:Remote,second:Remote):Remote = object : Remote {
            override fun up() {
                first.up()
                second.up()
            }

            override fun down() {
                first.down()
                second.down()
            }
        }
    }
}

这个伴生对象中的方法就是将两个实现了Remote接口进行合并,然后返回这个合并了的接口。

实现接口

Kotlin中接口的实现也很简单,就是在类的最后用:跟上要实现的接口

class Tv{
    var volume = 0
}

class TvRemote(val tv: Tv):Remote{
    override fun up() {
        tv.volume++
    }

    override fun down() {
        tv.volume--
    }
}

当然我们也可以使用匿名类来实现,我们用两个匿名对象实现然后测试一下上文提到的combine函数:

fun main() {
    val remote = TvRemote(Tv())
    println("volume is ${remote.tv.volume}")
    remote.doubleup()
    println("volume is ${remote.tv.volume}")
    remote.down()
    println("volume is ${remote.tv.volume}")

    val re1 = object:Remote{
        override fun up() {
            println("r1 up")
        }

        override fun down() {
            println("r1 down")
        }

    }

    val re2 = object:Remote{
        override fun up() {
            println("r2 up")
        }

        override fun down() {
            println("r2 down")
        }

    }
    val re3 = Remote.combine(re1,re2)
    re3.up()
    re3.down()
}

最后我们输出的结果:
在这里插入图片描述
这里就成功将r1和r2进行了合并。

抽象类

创建抽象类

Kotlin中的抽象类和Java中的抽象类也很类似,也是用abstract修饰符进行修饰的,这里给出一个示例:

abstract class Musician(val name:String,val activeFrom:Int){
    abstract fun instrumentType():String
}

class Cellist(name:String,activeFrom:Int):Musician(name, activeFrom){
    override fun instrumentType(): String = "String"
}

fun main() {
    val ma = Cellist("Yo-Yo-Ma",1961)
}

可以看到抽象类和抽象类中的抽象方法都要用abstract修饰符进行修饰,后面要实现这个抽象类的需要在冒号后面跟上抽象类的构造方法。在这个示例中Cellist类的构造方法接收两个参数并将这些参数传递给抽象类的构造方法。而且我们在Cellist中实现了抽象方法,实现方法要用override修饰符。

抽象类和接口

抽象类和接口的主要区别在于:

  1. 在接口中定义的属性没有幕后字段(即使用val或者var之后自动生成的字段),他们必须依赖抽象方法或者伴生对象来从实现类中得到属性。另外,抽象类中的属性可以使用幕后字段。
  2. 可以实现多个接口但是最多只能继承一个抽象类。

关于到底是使用接口还是抽象类,书中也给出了建议:接口不能包含字段但是类可以实现多个接口。另外,抽象基类可以有字段,但是一个类最多只能从一个抽象类拓展。所以每个方法都有利有弊。

如果想在多个类之间重用状态,那么抽象类是一个不错的选择。可以在抽象类中实现公共状态,并让实现类重写方法,同时重用抽象类提供的状态。

如果希望多个类遵守一个或者多个契约或规范,但又希望这些类选择自己的实现,那么接口是更好的选择。

但是在Kotlin和现代Java中,接口比抽象类稍微有一些优势。 接口能够进行方法实现,但是没有状态。一个类可以实现多个接口。在可能的情况下,最好使用接口而不是抽象类,这样会提供到更多的灵活性。

嵌套类和内部类

在Java中,如果我们想要在一个类中定义其内部类,只需要在简单的在外部类中再定义一个类即可:

class outer{
	class inner{

	}
}

这样就在outer类中创建了一个内部类。而如果要创建静态内部类的话只需要在内部类的class关键字前加上static修饰符即可。而在Kotlin中没有明确的静态内部类的概念,取而代之的是嵌套类。原来普通的内部类对应的是Kotlin中的内部类。

嵌套类

我们先来介绍Kotlin中的嵌套类,Kotlin中的嵌套类类似于Java中的静态内部类,嵌套类和外部类的关系较为独立,大部分情况下没有耦合调用的情况。比如说在Java中静态内部类的构造方法调用是不用依赖于外部类的实例的,Kotlin中的嵌套类也是这样。 我们先来给出一个嵌套类的示例:

class inout {
    var counter = 0
    val mIn = innn()
    override fun toString(): String {
        return "inout"
    }

    class stClass{

    }
}

在这个示例中外部类为inout,嵌套类为stClass。调用stClass的方法不用依赖于inout类的实例,比如:

fun main() {
    val st = inout.stClass()
}

可以看到,调用方式就是Java中调用静态内部类的方法。当然,这种嵌套类和静态内部类也有相同的限制,那就是其无法访问外部类中的参数,因为创建嵌套类的实例时无法保证一定有外部类的实例被创建出来。

内部类

与Java中的内部类对应,Kotlin中的内部类修饰的区别就是在嵌套类前面加上inner修饰符。与嵌套类不同,内部类的方法和成员的调用是依赖于外部类的实例的,也就是说,如果有一个内部类的实例那就必然有一个外部类的实例。除此之外,内部类可以引用外部类的方法和变量等。下面我们来写一个内部类的实例:

class inout {
    var counter = 0
    val mIn = innn()
    override fun toString(): String {
        return "inout"
    }

    inner class innn{
        public fun showOuter(){
            val st = this@inout.toString()
            println(st)
        }
    }
}

接下来我们调用这个内部类:

fun main() {
    inout().innn().showOuter()
}

可以看到在调用内部类的方法时就必须要先创建一个外部类的实例。再仔细观察innn类的showOuter方法,这个方法引用了外部类的toString方法,这是inner修饰符赋予它的权限。此处还要接着介绍一下Kotlin中的this语法。普通的this指代的是当前类的实例,就这个例子来说,单个this指代的就是内部类innn的实例。而this@inout可以翻译为"this of inout",也就是外部类inout的实例。所以这里的this@inout.toString方法最终调用到的就是外部类的toString方法。同理,除了this之外我们还可以使用super关键字,语法方面是和this一致的。单个super指的就是当前类实例的基类,super@ *** 指代的就是 *** 的超类,不过不推荐使用这种语法。

匿名内部类

最后再让我们介绍一下Kotlin中的匿名内部类。其实关于匿名对象我们在之前的内容中已经介绍过了,就是用object关键字来定义。不过匿名内部类和内部类不同,它不需要用inner修饰。这里演示一个实现了Remote接口的匿名内部类:

class Tv{

    private var volume = 0
    val remote:Remote
        get() = object:Remote{
            override fun up() {
                volume++
            }

            override fun down() {
                volume--
            }

            override fun toString(): String {
                return "Remote: ${this@Tv.toString()}"
            }
        }

    override fun toString(): String {
        return "Volume: ${volume}"
    }
}

这里在类的内部定义了一个实现了Remote接口的成员变量remote,并且之后设置了它的get方法。get方法的逻辑也很简单,就是每次都新创建一个实现了Remote接口的匿名内部类。这代码实际在每次访问Tv类的remote变量都会创建一个新的匿名内部类,每个创建的匿名内部类的地址也不同。最后匿名内部类也可以使用this和super语法。

继承

关于继承的内容我们在之前的抽象类的内容中有所提及,具体来说就是用冒号代表继承。在Kotlin中的继承是有一层额外的防御机制的,就如同变量要指定val还是var一样,Kotlin中的类也要指定是否可以被继承,一个类在默认情况下是不允许被继承的。

在Kotlin中用open关键字和final关键字来表示类是否可以被继承。如果一个类可以被继承,就必须写上open关键字:

open class Vehicle(val year:Int,open var color:String) {

}

但是如果不想让一个类被继承可以不写(默认情况下就是final修饰的)或者写上final关键字。并且,如果你想让一个类中的某个字段或者方法可以被重写的话就要指定该字段或者方法为open,并且在子类中还要标明重写方法关键字override:

open class Vehicle(val year:Int,open var color:String) {
    //允许重写字段--实际上是允许重写访问器和设置器
    open val km = 0

    final override fun toString(): String {
       return "year: $year,Color : $color,Km : $km"
    }

    fun repaint(newColor:String){
        color = newColor
    }
}

比如说这个类,我们用open关键字修饰Vehicle类表明这个类允许被继承。用open关键字修饰km字段。由于toString方法是Any类中的open方法,所以我们重写这个方法时需要用override关键字,同时我们又希望这个方法不要再被其子类重写了,所以加上final关键字,这样继承Vehicle类的类将无法重写toString方法。最后的一个repaint方法没有标明是open还是final的,这种情况下默认是用final修饰,所以子类将无法重写repaint方法。

重写字段

在上面的示例中我们提到了重写字段,这是一个比较新颖的概念,什么是重写字段呢?在之前的文章中,我们介绍了Kotlin中的访问字段实际上并不是直接访问该字段,而是用字段的get和set方法实现的,所以这里的重写字段的意思实际上就是重写字段的get和set方法。同时,我们可以在重写在类或者构造函数的参数列表中定义的属性,基类中的val属性可以用派生类中的var或者val来重写,而var属性只能用var属性重写。

接下来我们用一个子类来演示:

open class Car(year: Int,color: String) : Vehicle(year, color){
    override var km: Int = 0
        set(value) {
            if (value < 1){
                throw java.lang.RuntimeException("不能为负数")
            }
            field = value
        }

    fun drive(distance:Int){
        km += distance
    }
}

这里我们重写了km字段,将其属性从val改为var,并且重写了其set方法,使其在km值小于1时抛出异常。同时这里也表明了该如何实现继承类,就是在子类后面跟上父类的构造方法,Car构造函数中的参数将传递给Vehicle的构造函数。

Sealed类

Sealed类是Kotlin中用于实现继承的特殊基类。Sealed类是作为一个基层地带来给同文件的其他类实现继承的。具体来讲,Kotlin中的Sealed类对于同一个文件中定义的其他类进行拓展是开放的,但对于其他文件的类来说是封闭的。

sealed class Card(val suit:String)

class ace(suit: String):Card(suit)

class King(suit:String):Card(suit){
    override fun toString(): String {
        return "King of $suit"
    }
}

class Queen(suit:String):Card(suit){
    override fun toString(): String {
        return "Queen of $suit"
    }
}

class Pip(suit:String,val number:Int):Card(suit){
    init {
        if(number < 2 || number > 10){
            throw java.lang.RuntimeException("Error")
        }
    }
}

上面例子中的Card类就是一个Sealed类,是用于该文件中所有其他类的基类,Sealed类的构造函数没有标记为private但是默认是private。

枚举类

枚举类在Kotlin中十分简单,是用enum关键字修饰的,这里直接给出一个示例来说明,我们在前面的Vehicle的基础上进行修改:

open class Vehicle constructor(val year:Int,open var color:Color) {
    //允许重写字段--实际上是允许重写访问器和设置器
    open val km = 0

    final override fun toString(): String {
       return "year: $year,Color : $color,Km : $km"
    }

    fun repaint(newColor:Color){
        color = newColor
    }
}

open class  Car(year: Int,color: Color) : Vehicle(year, color){
    override var km: Int = 0
        set(value) {
            if (value < 1){
                throw java.lang.RuntimeException("不能为负数")
            }
            field = value
        }

    fun drive(distance:Int){
        km += distance
    }


}
enum class Color(val c:Char){RED('r'),YELLOW('y'),BLUE('b'),GREEN('g');

    override fun toString(): String {
        return when(c){
             'r' -> "红色"
             'y' -> "黄色"
             'b' -> "蓝色"
             'g' -> "绿色"
            else -> "non"
        }
    }
}

fun main() {
    val car = Car(2023,Color.RED)
    car.drive(10)
    println(car.toString())
}

这里我们创建了一个颜色枚举类Color并且声明了四种颜色实例,并且重写了其toString方法。这里注意我们要写枚举类的方法的话需要用分号将值列表与方法区分开来。除此之外我们还可以给枚举类来记录属性,只需要先在构造方法中声明幕后字段然后在值列表后面跟上相应的值即可。

最后运行出来的结果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/877795.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

配置网络设置和修改主机名

bash 题目&#xff1a; 在 node1 上配置网络&#xff0c;要求如下&#xff1a; 主机名&#xff1a;node1.domain8.rhce.cc IP地址: 172.25.250.10/24 ##注意掩码 网关&#xff1a; 172.25.250.250 DNS&#xff1a; 172.25.250.250 ##名称服务器 做法&#xff1a; nmtui 回车…

Modbus RTU校验码CRC16全字节查表的VB6实现方法

全字节表比半字节表数据精简&#xff0c;比纯计算速度快&#xff0c;实现起来方便&#xff0c;但是在VB6下或是在普通单片机上要变通处理&#xff0c;因为双字节变量处理会溢出而数据失真。 先看一下CSDN上网友XCS101的C程序 CRC-16/MODBUS 算法的三种实现方法_crc16modbus校验…

风险区分度—IV、KS和分布

IV和KS是风控中常用的评估指标&#xff0c;用于衡量变量的预测能力和区分度。一般来说&#xff0c;IV和KS值越大&#xff0c;表示该变量的预测能力越强。本文从IV和KS以及两者之间的关系方面作一些思考。 一、IV值 一般来说&#xff0c;IV计算用于筛选变量&#xff0c;常用来评…

记一次现场找不到配置的神奇报错,(其实配置是完整的)

记一次现场找不到配置的神奇报错&#xff0c;&#xff08;其实配置是完整的&#xff09; 问题的原因为因为配置文件中符号的问题。 报错找不到url1 **Notepad**打开的文件 如图是两份看起来一模一样的配置&#xff0c;其中一个就会报错找不到某某配置。 实际有细小的差别 …

纷享销客稳居2022 H2 SFA SaaS 本土CRM厂商市场份额 TOP 1

近期&#xff0c;国际知名研究机构IDC公布了2022年下半年《中国客户关系管理(CRM)SaaS市场跟踪研究报告》&#xff0c;报告全面解析了中国CRM SaaS以及细分市场SFA SaaS的市场现状&#xff0c;并对全球各大厂商在中国SFA市场的份额占比进行了排名。连接型CRM开创者纷享销客在SF…

AAAI 最佳论文列表(1984→2023最新)附论文下载

明天AAAI全文截稿了&#xff0c;不知道大家的论文投的咋样啦&#xff1f;我不得不提一句&#xff0c;今年的AAAI投稿量又破新高了&#xff0c;快14,000&#xff01;卷哭... 不过这个投稿量也在意料之中&#xff0c;AAAI属于中国计算机学会CCF的A类国际学术会议&#xff0c;在人…

Unity zSpace 开发

文章目录 1.下载 zSpace 开发环境1.1 zCore Unity Package1.2 zView Unity Package 2. 导入工程3. 发布设置4.功能实现4.1 用触控笔来实现对模型的拖拽&#xff1a; 5. 后续更新 1.下载 zSpace 开发环境 官网地址 1.1 zCore Unity Package zSpace 开发核心必须 1.2 zView …

Nacos使用SpringCloudAlibaba+Dubbo实现

Nacos简介 Nacos是阿里的一个开源产品&#xff0c;它是针对微服务架构中的服务发现、服务治理、配置管理的综合型解决方案。 官方介绍是这样的&#xff1a; Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集&#xff0c;帮助您实现动态服务发现、…

【QT】 Word模板编辑、转PDF格式

很高兴在雪易的CSDN遇见你 ,给你糖糖 欢迎大家加入雪易社区-CSDN社区云 前言 本文分享基于QT进行Word模板编辑以及Word转PDF的技术,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 目录 …

微信开发之一键退出群聊的技术实现

简要描述&#xff1a; 退出群聊 请求URL&#xff1a; http://域名地址/quitChatRoom 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型说明wI…

微信小程序实现左滑删除

一、效果 二、代码 实现思路使用的是官方提供的 <movable-area> <movable-view> Page({/*** 页面的初始数据*/data: {pushedData:[{messageTitle:饼干,messageContent:饼干爱吃}],//已推送数据},/*** 生命周期函数--监听页面加载*/onLoad() {},/*** 生命周期函数-…

设计列表和超链接

在网页中&#xff0c;大部分信息都是列表结构&#xff0c;如菜单栏、图文列表、分类导航、新闻列表、栏目列表等。HTML5定义了一套列表标签&#xff0c;通过列表结构实现对网页信息的合理排版。另外&#xff0c;网页中还包含大量超链接&#xff0c;通过它实现网页、位置的跳转&…

【计算机视觉|生成对抗】带条件的对抗网络进行图像到图像的转换

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;Image-to-Image Translation with Conditional Adversarial Networks 链接&#xff1a;Image-to-Image Translation with Conditional Adversarial Networks | IEEE Conference Publicati…

四、Dubbo扩展点加载机制

四、Dubbo扩展点加载机制 4.1 加载机制概述 Dubbo良好的扩展性与框架中针对不同场景使用合适设计模式、加载机制密不可分 Dubbo几乎所有功能组件都是基于扩展机制&#xff08;SPI&#xff09;实现的 Dubbo SPI 没有直接使用 Java SPI&#xff0c;在它思想上进行改进&#xff…

六轴机械臂码垛货物堆叠仿真

六轴机械臂码垛货物堆叠仿真 1、建立模型与仿真 clear,clc,close all addpath(genpath(.)) %建立模型参数如下&#xff1a; L(1) Link( d, 0.122, a , 0 , alpha, pi/2,offset,0); L(2) Link( d, 0.019 , a ,0.408 , alpha, 0,offset,pi/2); L(3) Link( d, …

C++的stack和queue+优先队列

文章目录 什么是容器适配器底层逻辑为什么选择deque作为stack和queue的底层默认容器优先队列优先队列的模拟实现stack和queue的模拟实现 什么是容器适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总 结)&#xff0c;…

【内联函数】

这里写目录标题 内联函数一、指定内联函数的方法很简单&#xff0c;只需要在函数定义处增加 inline 关键字一般是将非常短小的函数声明为内联函数内联函数与宏定义例题 内联函数 内联函数也称内嵌函数&#xff0c;它主要是解决程序的运行效率。 函数调用需要建立栈内存环境&am…

小白带你学习linux的MongoDB(三十四)

目录 一、概述 1、相关概念 2、特性 二、应用场景 三、安装 四、目录结构 五、默认数据库 1、admin&#xff1a; 2、local: 3、config: 六、数据库操作 1、库操作 2、文档操作 七、MongoDB数据库备份 1、备份命令 2、回数据库删除…

数据库内日期类型数据大于小于条件查找注意事项

只传date格式的日期取查datetime的字段的话默认是 00:00:00 日期类型字符串需要使用 ’ ’ 单引号括住 使用大于小于条件查询某一天的日期数据 前后判断条件不能是同一天 一个例子 数据库内数据&#xff1a; 查询2023-08-14之后的数据&#xff1a; select * from tetst…

Linux 内核第一版 (v0.01) 开源代码解读

探索Linux v0.01的内部结构&#xff0c;Linux内核经常被认为是一个庞大的开源软件。在撰写本文时&#xff0c;最新版本是v6.5-rc5&#xff0c;包含36M行代码。不用说&#xff0c;Linux是几十年来许多贡献者辛勤工作的成果。 Linux 内核首个开源版本 (v0.01) 的体积非常小&…