当前位置: 首页 > 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.倾斜摄影模型数…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)

说明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一个虚拟的网络世界&#xff0c;里面有虚拟的路由器、交换机、电脑&#xff08;PC&#xff09;等等。这些设备都在你的电脑里面“运行”&#xff0c;它们之间可以互相通信&#xff0c;就像一个封闭的小王国。 但是&#…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

SCAU期末笔记 - 数据分析与数据挖掘题库解析

这门怎么题库答案不全啊日 来简单学一下子来 一、选择题&#xff08;可多选&#xff09; 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘&#xff1a;专注于发现数据中…...

c++ 面试题(1)-----深度优先搜索(DFS)实现

操作系统&#xff1a;ubuntu22.04 IDE:Visual Studio Code 编程语言&#xff1a;C11 题目描述 地上有一个 m 行 n 列的方格&#xff0c;从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子&#xff0c;但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...

苍穹外卖--缓存菜品

1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得&#xff0c;如果用户端访问量比较大&#xff0c;数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据&#xff0c;减少数据库查询操作。 缓存逻辑分析&#xff1a; ①每个分类下的菜品保持一份缓存数据…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

PAN/FPN

import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

STM32---外部32.768K晶振(LSE)无法起振问题

晶振是否起振主要就检查两个1、晶振与MCU是否兼容&#xff1b;2、晶振的负载电容是否匹配 目录 一、判断晶振与MCU是否兼容 二、判断负载电容是否匹配 1. 晶振负载电容&#xff08;CL&#xff09;与匹配电容&#xff08;CL1、CL2&#xff09;的关系 2. 如何选择 CL1 和 CL…...