Kotlin 官方学习教程之扩展

2023-05-16

扩展

类似于 C# 和 Gosu, Kotlin 也提供了一种可以在不继承父类也不使用类似装饰器这样的设计模式的情况下对指定类进行扩展的功能。这是通过称为扩展名的特殊声明来实现的。 Kotlin 支持函数扩展和属性扩展。

函数扩展

要声明一个函数扩展,我们需要在函数前加一个接收者类型作为前缀。下面我们会为 MutableList 添加一个 swap 函数:

fun MutableList<Int>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' 对应 list
    this[index1] = this[index2]
    this[index2] = tmp
}

扩展函数中的 this 关键字对应接收者对象。现在我们可以在任意一个 MutableList 实例中调用 swap 对象。

val l = mutableListOf(1, 2, 3)
l.swap(0, 2) // 在 'swap()''this' 将会持有值 'l'

当然,这个函数对于任何 MutableList 都是有意义的,我们可以将它通用:

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' 对应 list
    this[index1] = this[index2]
    this[index2] = tmp
}

我们在函数名称之前声明通用类型属性,使之可以接收任何类型的参数。

扩展是被静态解析的

扩展实际上并没有修改它所扩展的类。定义一个扩展,你并没有在类中插入一个新的成员,只是让这个类的实例对象能够通过.调用新的函数。

需要强调的是扩展函数是静态分发的,举个例子,它们并不是接受者类型的虚拟方法。这意味着扩展函数的调用时由发起函数调用的表达式的类型决定的,而不是在运行时动态获得的表达式的类型决定。例如:

open class C

class D: C()

fun C.foo() = "c"

fun D.foo() = "d"

fun printFoo(c: C) {
    println(c.foo())
}

printFoo(D())

这个例子会输出 c,因为这里扩展函数的调用决定于声明的参数 c 的类型,也就是 C。

如果有同名同参数的成员函数和扩展函数,调用的时候必然会使用成员函数,比如:

class C {
    fun foo() { println("member") }
}

fun C.foo() { println("extension") }

如果我们对 C 的实例 c 调用 c.foo,会输出 “member”, 而不是”extension”。

但是你可以用不同的函数签名通过扩展函数的方式重载函数的成员函数,比如下面这样:

class C {
    fun foo() { println("member") }
}

fun C.foo(i: Int) { println("extension") }

对 C().foo(1) 的调用会输出 “extension”。

可空接收者

请注意扩展可以定义一个可空的接收类型。这样的扩展使得即使是一个空对象也可以调用它,然后在扩展内部进行 this == null 的检查。这样那就可以在 Kotlin 的任意调用 toString() 而无需进行空指针检查,空指针检查延后到扩展中进行。

fun Any?.toString(): String {
    if (this == null) return "null"
    // 在空指针检查后, 'this' 被自动转为非空类型,因此 toString() 可以被解析到任何类的成员函数中
    return toString()
}

属性扩展

和函数相似,Kotlin 支持属性扩展:

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

请注意,由于扩展并不会真正的往类中添加成员,因此也没有办法让扩展属性拥有备用字段。这也是为什么初始化函数不允许有扩展属性的原因。扩展属性只能通过明确提供 getter / setter 来定义。

例子:

val Foo.bar = 1 // 错误: 初始化函数不允许有扩展属性

伴随对象扩展

如果一个对象定义了一个伴随对象,你同样可以给伴随对象定义扩展函数和扩展属性。

class MyClass {
    companion object { }  // 被称作 "Companion"
}

fun MyClass.Companion.foo() {
    // ...
}

和普通伴随对象的成员一样,他们可以只通过类名就调用:

MyClass.foo()

扩展的域

大多数时候我们在 top level 中定义,例如直接早包名下定义:

package foo.bar

fun Baz.goo() { ... } 

要在声明的包外使用这样一个扩展,我们需要在使用它的地方导入它:

package com.example.usage

import foo.bar.goo // 导入所有叫做 "goo" 的扩展
                   // 或者
import foo.bar.*   // 导入 "foo.bar" 包下所有内容

fun usage(baz: Baz) {
    baz.goo()
}

声明扩展成员

在一个类中,你可以为另一个类声明一个扩展。在这样一个扩展中,有多个隐式接收器 - 对象成员可以在没有限定符的情况下被访问。声明扩展名的类的实例被称为调度接收方,声明扩展方法的接收方类型的实例称为扩展接收方。

class D {
    fun bar() { ... }
}

class C {
    fun baz() { ... }

    fun D.foo() {
        bar()   // 调用 D.bar
        baz()   // 调用 C.baz
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

在发送接收机的成员与分机接收者之间发生名称冲突的情况下,分机接收者优先。要引用发送接收器的成员,您可以使用此语法。

class C {
    fun D.foo() {
        toString()         // 调用D.toString()
        this@C.toString()  // 调用 C.toString()
    }

声明为成员的扩展可以在子类中声明为 open 型的并且可以被重写。这意味着这种函数的调度对于调度接收器类型是虚拟的,但是关于扩展接收器类型是静态的。

open class D {
}

class D1 : D() {
}

open class C {
    open fun D.foo() {
        println("D.foo in C")
    }

    open fun D1.foo() {
        println("D1.foo in C")
    }

    fun caller(d: D) {
        d.foo()   // 调用扩展函数
    }
}

class C1 : C() {
    override fun D.foo() {
        println("D.foo in C1")
    }

    override fun D1.foo() {
        println("D1.foo in C1")
    }
}

C().caller(D())   // 输出 "D.foo in C"
C1().caller(D())  // 输出 "D.foo in C1" - 调度接收器被虚拟地解析
C().caller(D1())  // 输出 "D.foo in C" - 扩展接收机静态解析

动机

在 Java 中我们经常使用一系列名为 “*Utils”的类,如 FileUtils,StringUtils 等等。著名的 java.util.Collections 也是其中的一员。但令人不快的是我们必须像下面这样使用它们:

// Java
Collections.swap(list, Collections.binarySearch(list, Collections.max(otherList)), Collections.max(list))

由于这些类名总是不变的,我们可以使用静态导入并这样使用:

// Java
swap(list, binarySearch(list, max(otherList)), max(list))

这样就好多了,但是这样我们只能从 IDE 自动完成代码获得很少甚至得不到帮助,如果我们能像下面这样就好了:

// Java
list.swap(list.binarySearch(otherList.max()), list.max())

但是我们不想在 List 内实现所有可能的方法,对吗?这是扩展帮助我们的地方。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kotlin 官方学习教程之扩展 的相关文章

  • 无人机小知识:Pitch Yaw Roll的经典解释

    无人机的一个最基本的基础知识就是对Pitch Yaw Roll三个方向的辨别 查了下网上很多资料 xff0c 都采用的下面的几张图进行说明 xff0c 我觉得很直观 xff0c 记录一下 xff0c 当做备忘录 机体坐标系 xff1a 固定
  • 编译警告:warning: Clock skew detected. Your build may be incomplete.

    在编译darknet ros程序时 xff0c 遇到如下警告 xff1a make 2 Warning File 39 darknet ros darknet ros CMakeFiles darknet ros lib dir depen
  • Yolo v4系列学习(四)Yolo v4移植ROS

    目录 1 建立名为catkin ws的工作空间2 下载darknet ros包3 下载Yolo v4源码4 开始编译5 漫长的解决Bug的过程6 配置Yolo v4 43 ROS7 开始使用Yolo v4 43 ROS8 调参参考网址 1
  • C++(14):make_unique

    C 43 43 14增加了make unique xff0c 用于创建unique ptr对象 xff1a include lt iostream gt include lt memory gt using namespace std in
  • Git:修改commit内容

    在提交code xff0c 使用git commit编写注释时 xff0c 如果发现注释的内容不太准确 xff0c 需要修改怎么办 xff1f 比如 xff0c 当前写的注释是 xff1a this is fix for XXX 在git
  • ros安装详细教程+问题解决

    官方英文连接 xff1a melodic Installation Ubuntu ROS Wiki 如果一下命令有疑问 xff1a 见上方官方文档 xff0c 也很简洁 ROS有许多版本 xff0c ubuntu20 04对应的版本Noet
  • 激光slam 理论详解(一)

    什么是slam 技术 slam Simultaneous Localization and Mapping 叫即时定位与建图 xff0c 它主要的作用是让机器人在未知的环境中 xff0c 完成定位 xff08 Localization xf
  • gazebo模型下载以及配置

    最近在学习ROS xff0c 主要是为了结合SLAM仿真使用 启动gazebo命令 roscore 在另一个终端执行 gazebo 就可以进入清爽的gazebo界面 xff08 如果屏幕出现黑屏并不是安装错误可以稍微等待一会 xff09 x
  • ROS中在一个包里引用其他包内的自定义消息或服务

    假设我们要在a package中引用b package里自定义的my message msg 方法1 可直接通过添加依赖的方式解决 首先在创建包时添加b package catkin create pkg std msgs roscpp r
  • 第三篇 视觉里程计(VO)的初始化过程以及openvslam中的相关实现详解

    视觉里程计 xff08 Visual Odometry VO xff09 xff0c 通过使用相机提供的连续帧图像信息 xff08 以及局部地图 xff0c 先不考虑 xff09 来估计相邻帧的相机运动 xff0c 将这些相对运行转换为以第
  • ROS : 参数服务器之动态调参(dynamic_reconfigure)

    另外参考链接 xff1a ROS动态参数配置 xff1a dynparam命令行工具的使用 xff08 示例 43 代码 xff09 肥肥胖胖是太阳的博客 CSDN博客 参数服务器实现的功能 xff1a 修改参数后 xff0c 不需要重新编
  • ROS中static_transform_publisher工具的使用

    static transform publisher的功能是发布两个坐标系之间的静态坐标变换 xff0c 这两个坐标系不会发生相对位置变化 命令格式为 xff1a static transform publisher x y z yaw p
  • Docker 下载redis

    首先去docker hub 获取下载命令Docker Hub 如图 xff1a docker pull redis 为最新版本 也可以 指定版本列如 xff1a docker pull redis 6 2 6 bullseye 下载成功 r
  • VSCode 如何查看git提交的历史记录或逐行记录

    下载两个插件就行了 Git History GitLens 安装成功之后 xff0c 任意选择一个文件 xff0c 你鼠标点击哪一行代码 xff0c 后面都会提示谁在什么时候做了什么 xff0c 鼠标悬浮提示上便会直接显示作者 xff0c
  • 电池、电机、螺旋桨搭配

    电池 电机 螺旋桨搭配 span class hljs number 1 span 电机 span class hljs number 1 span 电机KV值 xff1a 大KV配小桨 xff0c 小KV配大桨 KV值是每1V的电压下电机
  • 为什么指针变量做形参可以改变实参的数据

    形参不能传任何东西给实参 xff0c 实参传过去的东西都是一个副本 下面以一个交换数据的被调函数片段为例 在指针变量由实参传递给形参时传过去的实际是指针变量的值 xff0c 即一个地址 xff0c 在 t 61 p1 p1 61 p2 p2
  • 敲线性表代码时遇到的问题(C++)【exit,return】

    1 xff1a exit OVERFLOW exit简介 为C 43 43 的退出函数 xff0c 声明于stdlib h中 xff0c 对于C 43 43 其标准的头文件为cstdlib 声明为 void exit int value e
  • /etc目录详解

    Linux etc目录详解 etc目录 包含很多文件 许多网络配置文件也在 etc 中 etc rc or etc rc d or etc rc d 启动 或改变运行级时运行的scripts或scripts的目录 etc passwd 用户
  • Tomcat 8.0 Mac 安装与配置

    工具 原料 1 xff0c JDK xff1a 版本为jdk 8u40 macosx x64 dmg 下载地址http www oracle com technetwork java javase downloads jdk8 downlo
  • 2011届移动开发者大会

    2011年11月4号星期五 xff0c 早晨八点我们就早早的来到了会场 xff0c 因为有了上次云计算大会的经验 xff0c 所以我们早早的就来了 xff0c 因为人很多我们必须才能找到一个比较好的位置 由于来的太早工作人员很多都没有就位

随机推荐

  • 第五篇 openvslam建图与优化模块梳理

    建图模块 mapping module在初始化系统的时候进行实例化 xff0c 在构建实例的时候会实例化local map cleaner和local bundle adjuster 系统启动的时候会在另外一个线程中启动该模块 code s
  • 个人安卓学习笔记---java.io.IOException: Unable to open sync connection!

    在使用手机调试程序的时候出现了java io IOException Unable to open sync connection 这样的异常 xff0c 我尝试使用拔掉USB然后重新 xff0c 插入 xff0c 结果失败 再尝试 xff
  • "_OBJC_CLASS_$_Play", referenced from:

    IOS做了这么久也没写过什么博客 xff0c 不好不好 xff0c 今天开始写 遇到的问题 xff1a 34 OBJC CLASS Play 34 referenced from 解决方案 xff1a Tagert Build Phases
  • 树莓派SSH远程连接连接失败的解决办法

    树莓派SSH远程连接 将全新的树莓派系统烧录 xff0c 开机然后用SSH远程连接 xff0c 结果SSH连接提示 connection refused xff0c 导致连接树莓派失败 出现错误的原因是自 2016 11 25 官方发布的新
  • 在树莓派中安装ROS系统(Kinetic)

    在树莓派中安装ROS系统 重新梳理了一下树莓派的安装流程 xff0c 现在我们来开始吧 打开官网教程 http wiki ros org kinetic step1 安装源 xff08 中国 xff09 sudo sh c 39 etc l
  • ROS学习笔记-roscd指令

    对ROS文件系统而言 xff0c ROS中的roscd命令实现利用包的名字直接切换到相应的文件目录下 xff0c 命令使用方法如下 xff1a span class hljs tag roscd span span class hljs a
  • configure it with blueman-service

    用下面这个命令把Linux目录的名字由中文改成英文了 export LANG span class hljs subst 61 span en US xdg span class hljs attribute user span span
  • 关于Ubuntu16.04升级系统后启动报错问题的修复

    关于Ubuntu16 04升级系统后启动报错问题的修复 Ubuntu16 04升级后启动报错为 Failed to start Load Kernel Modules 使用systemctl status systemd modules l
  • Ubuntu Mate 自动登录

    树莓派安装Ubuntu Mate 设置自动启动 需要修改文件 usr share lightdm lightdm conf d 60 lightdm gtk greeter conf sudo vim usr share lightdm l
  • Linux系统调用实现简析

    1 前言 限于作者能力水平 xff0c 本文可能存在谬误 xff0c 因此而给读者带来的损失 xff0c 作者不做任何承诺 2 背景 本篇基于 Linux 4 14 43 ARM 32 43 glibc 2 31 进行分析 3 系统调用的实
  • Docker中容器的备份、恢复和迁移

    1 备份容器 首先 xff0c 为了备份 Docker中的容器 xff0c 我们会想看看我们想要备份的容器列表 要达成该目的 xff0c 我们需要在我们运行着 Docker 引擎 xff0c 并已创建了容器的 Linux 机器中运行 doc
  • 关于OpenCV的那些事——相机姿态更新

    上一节我们使用张正友相机标定法获得了相机内参 xff0c 这一节我们使用 PnP Perspective n Point 算法估计相机初始姿态并更新之 推荐3篇我学习的博客 xff1a 姿态估计 Pose estimation algori
  • Java中接口(Interface)的定义和使用

    有关 Java 中接口的使用相信程序员们都知道 xff0c 但是你们知不知道接口到底有什么用呢 xff1f 毫无疑问 xff0c 接口的重要性远比想象中重要 接下来我们便一起来学习Java中接口使用 Java接口是什么 Java接口是一系列
  • Java中向下转型的意义

    什么是向上转型和向下转型 在Java继承体系中 xff0c 认为基类 xff08 父类 超类 xff09 在上层 xff0c 导出类 xff08 子类 继承类 派生类 xff09 在下层 xff0c 因此向上转型的意思就是把子类对象转成父类
  • Java中单例模式的使用

    什么是单例模式 单例模式 xff0c 也叫单子模式 xff0c 是一种常用的软件设计模式 在应用这个模式时 xff0c 单例对象的类必须保证只有一个实例存在 许多时候整个系统只需要拥有一个的全局对象 xff0c 这样有利于我们协调系统整体的
  • Android RecyclerView完全解析

    什么是RecyclerView xff1f RecyclerView 是谷歌 V7 包下新增的控件 用来替代 ListView 的使用 在 RecyclerView 标准化了 ViewHolder 类似于 ListView 中 conver
  • 程序员也是会浪漫的->打造浪漫的Android表白程序

    一年前 xff0c 看到过有个牛人用HTML5绘制了浪漫的爱心表白动画 xff0c 后来又在华超的这篇文章上看到大神用Android写出了相同的效果 xff0c 于是也动手写了一下 xff0c 并加了一些功能 xff0c 感谢大神的指引 写
  • 清浅时光,岁月静好——我的2016

    前言 时光在不经意中流逝 xff0c 翻开旧日的笔记 xff0c 字里行间充满着情深意境的交错 仿佛回到了那曾经经历过的风风雨雨 xff0c 坎坎坷坷中逝去 xff0c 旧时的回忆依旧 xff0c 只是少了几分忧郁 xff0c 几分繁华 x
  • Android登录注册功能封装

    我们都知道Android应用软件基本上都会用到登录注册功能 xff0c 那么对一个一个好的登录注册模块进行封装就势在必行了 这里给大家介绍一下我的第一个项目中所用到的登录注册功能的 xff0c 已经对其进行封装 xff0c 希望能对大家有帮
  • Kotlin 官方学习教程之扩展

    扩展 类似于 C 和 Gosu xff0c Kotlin 也提供了一种可以在不继承父类也不使用类似装饰器这样的设计模式的情况下对指定类进行扩展的功能 这是通过称为扩展名的特殊声明来实现的 Kotlin 支持函数扩展和属性扩展 函数扩展 要声