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 Fruit

class Apple: Fruit()

fun Apple.foo() = " is apple "   // 扩展函数 foo

fun Fruit.foo() = " is fruit"   // 扩展函数 foo

fun 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 ")   // 扩展函数 foo

fun 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 = null
    println(demo.toString())
}

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

扩展属性

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

val User.foo = 1 // 错误:扩展属性不能有初始化器

val <T> List<T>.lastIndex: Int
    get() = size - 1              //正确

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

伴生对象的扩展

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

class NormalClass {
    companion object { }  // 将被称为 "Companion"
}

fun NormalClass.Companion.foo() {
    println(" 伴生对象Companion的扩展函数 ")
}

val NormalClass.Companion.no: Int
    get() = 10

fun main(args: Array<String>) {
    println(" no:${NormalClass.no} ")
    NormalClass.foo()
}

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

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

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

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

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

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

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

示例代码如下:

class RunProgram {
    companion object {
        val mFieldNum: Int = 1
        var 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: Int
    get() = 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.program

fun Ktz.goo() { …… }

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

package com.example.demo

import 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.bar
        baz()   // 调用 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.bar
        this@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,如有问题请留言讨论。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

npm ERR! path E:node_modules\node-sass

分析报错发现有关 python2 环境相关报错 解决办法&#xff1a;需要再电脑中安装python 2.X版本的环境 因为我本地电脑有python 3.9的环境&#xff0c;所以我使用 Anaconda安装python环境 1、安装 python 2.7 conda create -n py2 python2.72、激活虚拟环境 conda activate…

go-zero 统一返回

1、整体目录结构 2、全局处理主入口 package manageimport ("net/http""github.com/zeromicro/go-zero/rest/httpx" )type Body struct {Code int json:"code"Message string json:"message"Result interface{} jso…

Elastic Search的RestFul API入门:查询ES当前的情况cat和cluster

在我们使用 Elasticsearch 进行索引的增删改查的同时,有时候我们也需要查看集群的整体情况,这就需要用到 cat 和 cluster 这两个 API。 cat 和 cluster 是 Elasticsearch 中两个非常重要的 API,它们主要用于获取和管理集群以及节点的相关信息。 cat API 是一个强大且易用的…

电脑服务器漏洞有何影响?怎么修复?

一、什么是电脑服务器漏洞 电脑服务器漏洞是指在计算机服务器软、硬件中存在的漏洞或弱点&#xff0c;黑客或病毒利用这些漏洞可以入侵服务器系统&#xff0c;获取机密信息&#xff0c;破坏服务器的稳定和安全性。德迅云安全提醒用户&#xff0c;服务器漏洞的影响不容忽视。 …

纯html+js+css个人博客

首页 <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8"><title>主页</title><!-- 引入layui css文件 --><link rel"stylesheet" href"layui-…

基于Redis的高可用分布式锁——RedLock

目录 RedLock简介 RedLock工作流程 获取锁 释放锁 RedLock简介 Redis作者提出来的高可用分布式锁由多个完全独立的Redis节点组成&#xff0c;注意是完全独立&#xff0c;而不是主从关系或者集群关系&#xff0c;并且一般是要求分开机器部署的利用分布式高可以系统中大多数存…

vtk二进制文件查看DATASET为POLYDATA里的标量属性,C++语言

先看我有的vtk文件&#xff0c;打开看到数据集结构为PolyData 我们需要使用C来读取该二进制文件&#xff0c;然后进行获取里边的标量。 // 引入必要头文件 #include "QDebug" #include <vtkSmartPointer.h> #include <vtkPolyDataReader.h> #include &…

【macOS】mac M2 安装 Homebrew nvm

gitee 快速安装 打开终端&#xff0c;复制如下命令&#xff0c;按回车执行(需要输入密码) /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"在这个过程中可能还会弹出安装 git 的提示&#xff0c;直接安装等待即可 然后再…

借助gpt生成ppt:文心一言(chatgpt)、chatppt

提供一种简单的基于gpt快速生成ppt的方式。前置条件&#xff1a; 文心一言chatpptwps/office ppt Step1: 下载chatppt插件 https://chat-ppt.com/invitelinke?share_code47949695&channelchat-ppt.com 注册地址 下载完成后&#xff0c;安装即可&#xff0c;安装完成后…

JVM系列——垃圾收集器

对象存活判断 引用计数法 在对象中添加一个引用计数器&#xff0c;每当有一个地方引用它时&#xff0c;计数器值就加一&#xff1b;当引用失效时&#xff0c;计数器值就减一&#xff1b;任何时刻计数器为零的对象就是不可能再被使用的。 可达性分析算法 通过一系列称为“GC …

asp.net core监听本地ip地址

开发asp.net core的时候遇到一个问题我想提供访问供其他同事测试&#xff0c;但是默认都是localhost或者127.0.0.1。我想换成我的Ip地址访问但是不行&#xff0c;百度搜索需要更换监听的地址即修改launchSettings.json&#xff0c;修改为0.0.0.0:5248&#xff0c;这样不管local…

C语言王道第八周一题

Description 初始化顺序表&#xff08;顺序表中元素为整型&#xff09;&#xff0c;里边的元素是 1,2,3&#xff0c;然后通过 scanf 读取一个元素&#xff08;假如插入的是 6&#xff09;&#xff0c;插入到第 2 个位置&#xff0c;打印输出顺序表&#xff0c;每个 元素占 3 个…

笔记本电脑系统Win10重装教程

当前很多用户都会使用笔记本电脑办公&#xff0c;如果笔记本电脑携带的操作系统不好用&#xff0c;就会影响到用户的办公效率&#xff0c;这时候可以给笔记本电脑重新安装一款好用的系统。以下小编带来笔记本电脑系统Win10重装教程&#xff0c;让用户们轻松给笔记本电脑重新安装…

大模型学习笔记一:大模型应开发基础(模型归类选型、安全因素选型、)

文章目录 一、大模型一些概念介绍二、市面上大模型对比三、大模型使用安全选型四、使用大模型的方式&#xff08;一问一答、Agent Function Calling、RAG、Fine-tuning五、大模型使用路线九、补充说明1&#xff09;注意力机制讲解 一、大模型一些概念介绍 1&#xff09;产品和大…

Linux安装Jdk8

本文以centos7为例&#xff0c;一步一步进行jdk1.8的安装。 1. 下载安装 笑小枫网站下载&#xff1a;https://xiaoxiaofeng.com/resource/16 官网下载链接&#xff1a; https://www.oracle.com/cn/java/technologies/downloads/#java8 上传jdk的压缩包到服务器的/usr/local目…

封装通用mixins,在vue中实现a-table组件的可伸缩列(详细且使用便捷)

1、实现效果 2、使用场景 vue2 antd-vue 1.x版本由于antd-vue 1.x版本的组件库没有提供可伸缩列的功能&#xff0c;才需要我们手动开发在antd-vue 3.x版本以上的表格已经支持这个功能&#xff0c;不需要我们再去手动开发 3、话不多说&#xff0c;上代码 首先安装vue-dragga…

【超详细教程】GPT-SoVITs从零开始训练声音克隆教程(主要以云端AutoDL部署为例)

目录 一、前言 二、GPT-SoVITs使用教程 2.1、Windows一键启动 2.2、AutoDL云端部署 2.3、人声伴奏分离 2.4、语音切割 2.5、打标训练数据 2.6、数据集预处理 2.7、训练音频数据 2.8、推理模型 三、总结 一、前言 近日&#xff0c;RVC变声器的创始人&#xff08;GitH…

网络安全防御保护 Day4

要点一&#xff1a;防火墙的智能选路 就近选路&#xff1a; 在访问不同运营商的服务器时直接通过对应运营商的链路&#xff0c;以此来提高通信效率&#xff0c;避免绕路。 策略路由&#xff08;PBR&#xff09;&#xff1a; 这是一种基于用户定义的策略&#xff08;如业务需求、…

嵌入式中Qt5.7.1添加支持openssl方法

1、openssl编译 版本&#xff1a;openssl-1.0.2g 一定要选对Qt版本对应的openssl版本&#xff0c;由于开始选的openssl版本不对&#xff0c;导致编译Qt时出现很多错误。 交叉编译 ./config no-asm shared --prefix/opt/Xilinx2018_zynq/zynq_openssl_1.0.2/ --cross-compile…

Unity-WebGL

问题&#xff1a;提示gzip压缩报错解决&#xff1a;关闭打包的地方压缩&#xff0c;如下图问题&#xff1a;窗口未全屏解决&#xff1a;使用百分比画布替换固定尺寸画布 参考&#xff1a;新版Unity打包Webgl端进行屏幕自适应_unity webgl分辨率自适应-CSDN博客问题&#xff1a;…