当前位置: 首页 > news >正文

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,如有问题请留言讨论。

相关文章:

Kotlin快速入门系列6

Kotlin的接口与扩展 接口 与Java类似&#xff0c;Kotlin使用interface关键字定义接口&#xff0c;同时允许方法有默认实现&#xff1a; interface KtInterfaceTest {fun method()fun methodGo(){println("上面方法未实现&#xff0c;此方法已实现")} } 接口实现 …...

w24文件上传之PHP伪协议

PHP支持的伪协议 file:// - 访问本地文件系统 http:// - 访问网址 ftp:// - 访问文件 php:// -访问各个输入/输出流 zlib:// -压缩流 data:// - 数据 glob:// -查找匹配的文件路径模式 phar:// - php归档 ssh2:// - Secure shell 2 rar:// - RAR ogg:// - 音频流 expect:// - …...

SQL注入攻击 - 基于时间的盲注

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 1、SQL 盲注基础 盲注(Blind SQL)是注入攻击的一种形式,攻击者通过向数据库发送true或false等问题,并根据应用程序返回的信息来判断结果。这种攻击方式出现的原因是应用程序配…...

比VS Code快得多

Zed 是一款支持多人协作的代码编辑器&#xff0c;底层采用 Rust&#xff0c;且默认支持 Rust&#xff0c;还自带了 rust-analyzer&#xff0c;主打“高性能”。1 月 24 日&#xff0c;备受关注的 Zed 项目宣布正式开源。 Zed 代码库将采用 Copyleft 许可证&#xff0c;其中编辑…...

将一个excel文件里面具有相同参数的行提取后存入新的excel

功能描述&#xff1a; 一个excel里面有很多行数据&#xff0c;其中“交易时间”这一列有很多交易日期&#xff0c;有些行的交易日期是一样的&#xff0c;那么就把所有交易日期相同的行挑出来&#xff0c;形成一个新的以交易日期命名的文件。import pandas as pd import os# 读取…...

Linux下安装edge

edge具有及其强大的功能&#xff0c;受到很多人的喜爱&#xff0c;它也开发Linux版本&#xff0c;下面是安装方法&#xff1a; 1.去edge官网下载Linux(.deb)文件。 https://www.microsoft.com/zh-cn/edge/download?formMA13FJ 2.下载之后输入以下指令&#xff08;后面是安装…...

Java / Spring Boot + POI 给 Word 添加水印

1、前言(瞎扯) 有个需求&#xff1a;整一个给 Word 加水印的demo&#xff0c;于是我就网上找呗~ 看到那个 Aspose 好像是收费的&#xff0c;然后就把目光转向了 POI&#xff0c;看到各种形形色色的也不知道哪个能用。整了一会&#xff0c;自己拷贝出一个比较精简的能用的 demo …...

Unity打包Android,jar文件无法解析的问题

Unity打包Android&#xff0c;jar无法解析的问题 介绍解决方案总结 介绍 最近在接入语音的SDK时&#xff0c;发现的这个问题. 当我默认导入这个插件的时候&#xff0c;插件内部的文件夹&#xff08;我下面话红框的文件夹&#xff09;名字原本为GCloudVoice&#xff0c;这时候我…...

postman之接口参数签名(js接口HMAC-SHA256签名)

文章目录 postman之接口参数签名&#xff08;js接口签名&#xff09;一、需求背景二、签名生成规则三、postman js接口签名步骤1. postman设置全局、或环境参数2. 配置Pre-request Scripts脚本 四、Pre-request Scripts脚本 常见工作整理1. js获取unix时间戳2. body json字符串…...

从c到c++——6:auto

在编写c程序时&#xff0c;需要在初始化变量时清楚地知道该变量的数据类型&#xff0c;有时这到这一点并不容易&#xff0c;在涉及到函数指针&#xff0c;多级指针时往往很难一下子给出准确的值。使用auto关键字很好的提高编程效率。 auto关键字会根据右边的类型自动生成适合的…...

前端面试题:字符串中字符出现的最多次数

前端基础算法面试题,一个字符串中,出现最多的字符以及出现的次数。 1.首先对字符串转换成字符串数组,然后对字符串数组进行排序,得到一个有序的数组 2.然后对排序后的字符串数组,进行查找 3.每次找到字符出现的最后的位置,然后进行计数 4.得到最终结果 function get…...

获取鼠标点击图片时候的坐标,以及利用html 中的useMap 和area 实现图片固定位置的点击事件

一 编写原因 应项目要求&#xff0c;需要对图片的固定几个位置分别做一个点击事件&#xff0c;响应不同的操作&#xff0c;如下图&#xff0c;需要点击红色区域&#xff0c;弹出不同的提示框&#xff1a; 二 获取点击图片时候的坐标 1. 说明 实现这以上功能的前提是需要确定需…...

webassembly003 TTS BARK.CPP

TTS task TTS&#xff08;Text-to-Speech&#xff09;任务是一种自然语言处理&#xff08;NLP&#xff09;任务&#xff0c;其中模型的目标是将输入的文本转换为声音&#xff0c;实现自动语音合成。具体来说&#xff0c;模型需要理解输入的文本并生成对应的语音输出&#xff0…...

HiveSQL题——排序函数(row_number/rank/dense_rank)

一、窗口函数的知识点 1.1 窗户函数的定义 窗口函数可以拆分为【窗口函数】。窗口函数官网指路&#xff1a; LanguageManual WindowingAndAnalytics - Apache Hive - Apache Software Foundationhttps://cwiki.apache.org/confluence/display/Hive/LanguageManual%20Windowin…...

【C语言】(9)分支结构

一.if-else 语句 if-else 适用于简单和复杂的条件判断。 a. 基本 if 语句 用途&#xff1a;基本的条件测试。语法&#xff1a;if (condition) {// 代码块 }示例&#xff1a;if (score > 60) {printf("及格\n"); }b. if-else 语句 用途&#xff1a;二选一的条件…...

Flink 集成 Debezium Confluent Avro ( format=debezium-avro-confluent )

博主历时三年精心创作的《大数据平台架构与原型实现:数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行,点击《重磅推荐:建大数据平台太难了!给我发个工程原型吧!》了解图书详情,京东购书链接:https://item.jd.com/12677623.html,扫描左侧二维…...

R语言(数据导入,清洗,可视化,特征工程,建模)

记录一下痛失的超级轻松的数据分析实习&#xff08;线上&#xff09;&#xff0c;hr问我有没有相关经历&#xff0c;我说我会用jupyter book进行数据导入&#xff0c;清洗&#xff0c;可视化&#xff0c;特征工程&#xff0c;建模&#xff0c;python学和用的比较多&#xff0c;…...

springboot 整合 Activiti6

1.添加maven依赖 <dependency><groupId>org.activiti</groupId><artifactId>activiti-spring-boot-starter-basic</artifactId><version>6.0.0</version> </dependency>2.添加配置 spring:activiti:check-process-definitio…...

微信小程序canvas画布实现直线自由缩放、移动功能

目录 实现效果 一、获取画布信息并绘制背景 二、绘制直线...

Cesium数据加载

文章目录 0.引言1.影像加载1.1Bing地图1.2天地图1.3ArcGIS在线地图1.4高德地图1.5OSM影像1.6MapBox影像 2.OGC地图服务2.1WMS2.2WMTS2.3TMS 3.GeoJSON数据加载4.KML数据加载5.TIFF数据加载6.点云数据加载7.地形数据加载7.1在线地形数据加载7.2本地地形数据加载 8.倾斜摄影模型数…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

Matlab | matlab常用命令总结

常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...

leetcodeSQL解题:3564. 季节性销售分析

leetcodeSQL解题&#xff1a;3564. 季节性销售分析 题目&#xff1a; 表&#xff1a;sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信

文章目录 Linux C语言网络编程详细入门教程&#xff1a;如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket&#xff08;服务端和客户端都要&#xff09;2. 绑定本地地址和端口&#x…...

技术栈RabbitMq的介绍和使用

目录 1. 什么是消息队列&#xff1f;2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

回溯算法学习

一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...