Kotlin快速入门系列6

Kotlin的接口与扩展

接口

与Java类似,Kotlin使用interface关键字定义接口,同时允许方法有默认实现:

interface KtInterfaceTest {fun method()fun methodGo(){println("上面方法未实现,此方法已实现")}
}

接口实现

一个类或者对象可以实现一个或多个接口。

class Demo : KtInterfaceTest{override fun method() {println("在类Demo中实现KtInterfaceTest接口的method()")}
}

我们在主函数中调用一下:

fun main(args: Array<String>) {val demo =  Demo()demo.method()demo.methodGo()
}

对应控制台输出为:

接口中的属性

接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值。在实现接口时,必须重写属性。

interface KtInterfaceTest {var urlString : String   //抽象属性,不允许实例化具体值
}class Demo : KtInterfaceTest{override var urlString: String = "www.google.com"
}

函数重写冲突

接口的重写跟类的重写类似,如果父类中声明了许多类型,有可能出现一个方法的多种实现,则必须重写这个成员并且提供自己的实现,而要使用父类中提供的方法,则需要用super<Base>来表示。

interface A {fun foo() { print("A") }fun bar()
}interface B {fun foo() { print("B") }fun bar() { print("bar") }
}class C : A {override fun bar() { print("bar") }
}class D : A, B {override fun foo() {super<A>.foo()super<B>.foo()}
}

如上代码,接口A,B都声明了foo()、bar()函数,接口B两个方法都有实现,接口A只实现了foo(),而bar()在A接口中并没有声明是抽象函数,所以C类要重写并实现bar()。而D类,同时继承A,B两个接口,不用重写bar()方法,因为继承的B接口已经实现同名方法。但由于继承了两个接口的foo()实现,所以需要用super<base类>关键字来区分。

扩展

kotlin同C#和Gson类似,能够扩展一个类的新的属性或函数(方法)而无需继承该类,且无需使用装饰模式在内的任何类型的设计模式。扩展是一种静态行为,对被扩展的类代码本身不会造成任何影响。

扩展函数

扩展函数可以在已有类中添加新的方法,不会对原类做修改,扩展函数的定义形式:

fun receiverType.functionName(params){body
}

· receiverType:表示函数扩展的对象(接收者)

· functionName:扩展函数的名称

· params:扩展函数的参数,可以为null

下面是一个对User类的扩展:

class User(var number:Int)fun User.Printf(){println(" 用户号 : $number")
}fun main(args: Array<String>) {var user = User(123)user.Printf()
}

对应输出结果为:

同时,这个扩展函数还可改为:

fun User.Printf(){println(" 用户号 : "+this.number)
}

运行结果也是一致的。这里要说明下,这里的this关键字指代的是函数扩展对象(receiver object)(也就是调用扩展函数时, 在点号之前指定的对象实例)。

扩展是静态解析的

扩展不能真正的修改他们的(扩展)类。扩展方法并没有在类中插入新的成员,仅仅是可以通过该类型的变量使用点的表达式调用这个函数。

扩展函数一直强调是静态解析的,并不是接受者类型的虚拟成员。在调用扩展函数的时候,具体被调用的是哪一个函数,这一点由调用函数的对象表达式来决定,而不是动态的类型来决定的,如下示例:

open class Fruitclass Apple: Fruit()fun Apple.foo() = " is apple "   // 扩展函数 foofun Fruit.foo() = " is fruit"   // 扩展函数 foofun printFoo(f: Fruit) {println(f.foo())  // 类型是 Fruit 类
}fun main(arg:Array<String>){printFoo(Apple())
}

对应的输出结果是:

若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。

class Apple{fun foo(){println(" is apple in class ")   // 成员函数 foo}
}fun Apple.foo() = println(" is apple in extend ")   // 扩展函数 foofun main(arg:Array<String>){var apple = Apple()apple.foo()
}

对应的控制台输出结果为:

扩展一个空对象

在扩展函数内,可通过this关键字来判断接收者(receiverType)是否为null。这样,即使接收者为null,也可以调用扩展函数。如下:

fun Any?.toString(): String {if (this == null) return "null"// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()解析为 Any 类的函数toString()return toString()
}fun main(arg:Array<String>){var demo = nullprintln(demo.toString())
}

对应的控制台输出结果为:

扩展属性

除了扩展函数,kotlin也支持扩展属性。扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。初始化属性因为属性没有后端字段(backing field),所以不允许被初始化,只能由显式提供的 getter/setter 定义。

val User.foo = 1 // 错误:扩展属性不能有初始化器val <T> List<T>.lastIndex: Intget() = size - 1              //正确

值得注意的是,扩展属性只能被声明为 val。

伴生对象的扩展

如果一个类有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。伴生对象通过"类名."形式调用伴生对象,伴生对象声明的扩展函数,通过用类名限定符来调用:

class NormalClass {companion object { }  // 将被称为 "Companion"
}fun NormalClass.Companion.foo() {println(" 伴生对象Companion的扩展函数 ")
}val NormalClass.Companion.no: Intget() = 10fun main(args: Array<String>) {println(" no:${NormalClass.no} ")NormalClass.foo()
}

在控制台对应的输出结果为:

补充:伴生对象内的成员相当于 Java 中的静态成员,其生命周期伴随类始终,在伴生对象内部可以定义变量和函数,这些变量和函数可以直接用类名引用。

对于伴生对象扩展函数,有两种形式,一种是在类内扩展,一种是在类外扩展,这两种形式扩展后的函数互不影响(甚至名称都可以相同),即使名称相同,它们也完全是两个不同的函数,并且有以下特点:

 (1)类内扩展的伴随对象函数和类外扩展的伴随对象可以同名,它们是两个独立的函数,互不影响;

 (2)当类内扩展的伴随对象函数和类外扩展的伴随对象同名时,类内的其它函数优先引用类内扩展的伴随对象函数,即对于类内其它成员函数来说,类内扩展屏蔽类外扩展;

 (3)类内扩展的伴随对象函数只能被类内的函数引用,不能被类外的函数和伴随对象内的函数引用;

 (4)类外扩展的伴随对象函数可以被伴随对象内的函数引用。

示例代码如下:

class RunProgram {companion object {val mFieldNum: Int = 1var mFieldString = "this is mFieldString"fun companionFun1() {println("this is 1st companion function.")foo()}fun companionFun2() {println("this is 2st companion function.")companionFun1()}}fun RunProgram.Companion.foo() {println("伴随对象的扩展函数(内部)")}fun test2() {RunProgram.foo()}init {test2()}
}
val RunProgram.Companion.no: Intget() = 10
fun RunProgram.Companion.foo() {println("foo 伴随对象外部扩展函数")
}
fun main(args: Array<String>) {println("no:${RunProgram.no}")println("field1:${RunProgram.mFieldNum}")println("field2:${RunProgram.mFieldString}")RunProgram.foo()RunProgram.companionFun2()
}

对应控制台输出结果为:

扩展的作用域

通常扩展函数或扩展属性就定义在顶级包下:

package ktfoo.programfun Ktz.goo() { …… }

如果要使用所定义包之外的一个扩展, 可通过import导入扩展的函数名进行使用:

package com.example.demoimport ktfoo.program.goo // 导入所有名为 goo 的扩展
// 或者
import ktfoo.program.*   // 从 ktfoo.program 导入一切fun usage(baz: Ktz) {baz.goo()
}

扩展声明为成员

在kotlin中,一个类内部你可以为另一个类声明扩展。在这个扩展中,有个多个隐含的接受者,其中扩展方法定义所在类的实例称为分发接受者,而扩展方法的目标类型的实例称为扩展接受者

class Car {fun bar() { println("Car bar") }
}class Plane {fun baz() { println("Plane baz") }fun Car.foo() {bar()   // 调用 Car.barbaz()   // 调用 Plane.baz}fun caller(car: Car) {car.foo()   // 调用扩展函数}
}fun main(args: Array<String>) {val Plane: Plane = Plane()val car: Car = Car()Plane.caller(car)
}

可以推算出控制台输出为:

在上段代码中, Plane 类内,创建了Car类的扩展。此时,Plane被成为分发接受者,而Car为扩展接受者。从上例中,可以清楚的看到,在扩展函数中,可以调用派发接收者的成员函数。

假如在调用某一个函数,而该函数在分发接受者和扩展接受者均存在,则以扩展接收者优先,要引用分发接收者的成员你可以使用限定的 this 语法。示例如下:

class Car {fun bar() { println("Car bar") }
}class Plane {fun bar() { println("Plane bar") }  //注意fun Car.foo() {bar()   // 调用 Car.barthis@Plane.bar()   // 调用 Plane.baz}fun caller(car: Car) {car.foo()   // 调用扩展函数}
}fun main(args: Array<String>) {val Plane: Plane = Plane()val car: Car = Car()Plane.caller(car)
}

对应的控制台输出:

值得一提的是,以成员的形式定义的扩展函数, 可以声明为 open , 而且可以在子类中覆盖。(也可以说,在这类扩展函数的派发过程中, 针对分发接受者是虚拟的(virtual), 但针对扩展接受者仍然是静态的。)

open class Car {
}class SUV : Car(){}open class Plane {open fun Car.drive() {println(" Car driving in the Plane ")}open fun SUV.drive() {println(" SUV driving in the Plane ")}fun caller(car: Car){car.drive()    //调用扩展函数}
}class FighterPlane : Plane(){override fun Car.drive() {println(" Car driving in the FighterPlane ")}override fun SUV.drive() {println(" SUV driving in the FighterPlane ")}
}fun main(args: Array<String>) {Plane().caller(Car())FighterPlane().caller(Car())        //分发接收者虚拟解析Plane().caller(SUV())               //扩展接收者静态解析
}

对应控制台输出为:

End,如有问题请留言讨论。

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

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

相关文章

JS第一课简单看看这是啥东西

1.什么是JavaScript JS是一门编程语言&#xff0c;是一种运行在客户端(浏览器)的编程语言&#xff0c;主要是让前端的画面动起来&#xff0c;注意HTML和CSS不是编程语言&#xff0c;他俩是一种标记语言。JS只要有浏览器就能运行不用跟Python或者Java一样上来装一个jdk或者Pyth…

12306提示人证核验失败问题解决方案

问题环境&#xff1a;手机已经 Root 并且安装了其他软件 认证时提示 官方客服回复: 可能是我的人脸发生了太大变化导致&#xff0c;建议我去身份证的公安部门更新人脸信息&#xff0c;但是想一想又不对&#xff0c;如果发生了大变化所有 App 使用的都是统一的公安部的人脸信息…

探索数字经济:从基础到前沿的奇妙旅程

新一轮技术革命方兴未艾&#xff0c;特别是以人工智能、大数据、物联网等为代表的数字技术革命&#xff0c;催生了一系列新技术、新产业、新模式&#xff0c;深刻改变着世界经济面貌。数字经济已成为重组全球要素资源、重塑全球经济结构、改变全球竞争格局的关键力量。预估到20…

关于maven项目构建的解释

在Idea中使用模块化构建项目 项目介绍&#xff1a; sky-server依赖sky-pojo和sky-common&#xff0c;继承sky-take-outsky-pojo继承sky-take-outsky-common继承sky-take-out 由于Idea编译器自动识别引入的模块&#xff0c;所以在Idea中可以运行项目。 在Idea中使用maven打包…

Redis -- 背景知识

目录 特性 为啥Redis快? 应用场景 Redis不能做什么&#xff1f; Redis是在内存中存储数据的一个中间件&#xff0c;用作为数据库&#xff0c;也可以用作为缓存&#xff0c;在分布式中有很高的威望。 特性 In-memory data structures&#xff1a;在内存中存储数据key-val…

设计模式——职责链模式(Chain of Responsibility Pattern)

概述 职责链模式(Chain of Responsibility Pattern)&#xff1a;避免请求发送者与接收者耦合在一起&#xff0c;让多个对象都有可能接收请求&#xff0c;将这些对象连接成一条链&#xff0c;并且沿着这条链传递请求&#xff0c;直到有对象处理它为止。职责链模式是一种对象行为…

少儿编程 中国电子学会图形化编程2021年3月等级考试Scratch三级真题解析(选择题、判断题)

1.在《采矿》游戏中&#xff0c;当角色捡到黄金时财富值加1分&#xff0c;捡到钻石时财富值加2分&#xff0c;下面哪个程序实现这个功能&#xff1f; A&#xff1a; B&#xff1a; C&#xff1a; D&#xff1a; 2.设计一个和在20以内&#xff08;包括20&#xff09;的整数加法…

通过docker构建基于LNMP的WordPress项目

计划通过自定义网络模式&#xff0c;创建一个172.18.0.0/16网段 nginx&#xff1a;172.18.0.2:80 php&#xff1a;172.18.0.3:9000 mysql&#xff1a;172.18.0.4:3306 创建nginx的镜像 准备好nginx的安装包 准备nginx的网页目录和wordpress网站目录以及nginx.conf文件 编…

中国地区cetos7.9 install kubeadmin

第 1 步&#xff1a;禁用 SELinux&#xff08;可选但推荐&#xff09; 如何在 CentOS 7 上查找 SELinux 状态 sestatus另一种选择是运行以下 cat 命令&#xff1a; vi /etc/selinux/config SELINUXdisabled rebootcentos7 linux 安装k8s前下面操作的作用是&#xff1f; cat…

用Python库pillow处理图像

入门知识 颜色。如果你有使用颜料画画的经历&#xff0c;那么一定知道混合红、黄、蓝三种颜料可以得到其他的颜色&#xff0c;事实上这三种颜色就是美术中的三原色&#xff0c;它们是不能再分解的基本颜色。在计算机中&#xff0c;我们可以将红、绿、蓝三种色光以不同的比例叠加…

腾讯云云监控实践:使用云审计 CloudAudit SDK 精准管理腾讯云资源

文章目录 一、什么是腾讯云的操作审计 CloudAudit二、CloudAudit 有哪些优势三、CloudAudit 应用场景举例3.1 安全分析3.2 资源变更跟踪3.3 合规性审计 四、使用云审计 SDK 进行云监控4.1 安装环境包 PHP4.2 下载并解压云审计 PHP SDK4.3 创建的腾讯云持久证书&#xff08;如果…

【添加公众号】CSDN官方指定推广功能

一、场景 二、说明 三、要求&#xff08;其中之一&#xff09; 三、实战 Stage 1&#xff1a;进入推广管理 Stage 2&#xff1a;申请推广 1、微信公众号推广 2、微信号推广 Stage 3&#xff1a;提交审核 Stage 4&#xff1a;查看结果 Stage 5&#xff1a;开启推广 S…

深度学习与神经网络pytorch版 2.3 线性代数

深度学习与神经网络pytorch版 2.3 线性代数 目录 深度学习与神经网络pytorch版 2.3 线性代数 1. 简介 2. 线性代数 2.3.1 标量 ​编辑2.3.2 向量 2.3.3 矩阵 2.3.4 张量及其性质 2.3.5 降维 2.3.6 非降维求和 2.3.7 点积 2.3.8 矩阵-向量积 2.3.9 矩阵-矩阵乘法 …

linux平台 LED情报板网关项目总结

LED情报板目前已经基本稳定 主要存在两个版本LED1.5 和LED2 两个版本的主要差别是 &#xff1a; 1.1.5的配置文件存在本地 2.0的存在平台 需要通过接口从平台获取 2.1.5的下发消息的时候需要同步返回 2.0的是异步返回&#xff0c;所以1.5的要尽可能完成 不然会导致发 布平台…

境外投资企业备案结果公开名录列表数据

境外投资企业备案结果公开名录列表数据 1、时间&#xff1a;更新至2023年10月16日 2、指标&#xff1a;境外投资企业_机构、境内投资者名称、投资国别地区 3、来源&#xff1a;商务部 4、指标解释 境外投资企业&#xff08;机构&#xff09;备案结果公开名录列表&#xff…

SpringBoot 结合 liteflow 规则引擎使用

1、前言 在日常的开发过程中&#xff0c;经常会遇到一些串行或者并行的业务流程问题&#xff0c;而业务之间不必存在相关性。 在这样的场景下&#xff0c;使用策略和模板模式的结合可以很好的解决这个问题&#xff0c;但是使用编码的方式会使得文件太多,在业务的部分环节可以…

LabVIEW扫频阻抗测试系统

实现扫频阻抗法用于检测变压器绕组变形&#xff0c;结合了短路阻抗法和频响法的优点&#xff0c;但受限于硬件精度&#xff0c;尤其是50 Hz短路阻抗测试存在稳定性和准确性的问题。通过LabVIEW编程&#xff0c;控制宽频带信号发生器和高速采集卡&#xff0c;提高测试结果的稳定…

【论文阅读|细胞实例分割算法ASF-YOLO】

论文题目&#xff1a;ASF-YOLO: A novel YOLO model with attentional scale sequence fusion for cell instance segmentation 论文链接&#xff1a; https://arxiv.org/abs/2312.06458 代码链接&#xff1a;https://github.com/mkang315/ASF-YOLO 摘要&#xff08;Abstract&a…

C++(6) 继承

文章目录 继承1. 继承1.1 什么是继承1.2 C 继承方式1.2.1 基本案例1.2.2 继承权限组合1.2.3 继承中构造函数的说法1.2.4 继承中析构函数的执行顺序1.2.5 继承中变量名称冲突问题1.2.6 继承中函数【重写】 继承 1. 继承 1.1 什么是继承 面向对象程序设计中最重要的一个概念是继…

使用xlsx、xlsx-style导出表格添加背景色;合并单元格部分样式缺失问题解决

这篇说一下使用xlsx-style导出excel时样式的设置。需要安装xlsx、xlsx-style、file-saver插件&#xff08;file-saver可以不装&#xff0c;用a标签代替也可以&#xff09;&#xff0c;安装时可能会碰到一些报错问题&#xff0c;可以去看下我之前一篇博客&#xff1a;纯前端导出…