kotlin 知识点三 扩展函数和运算符重载

embedded/2025/2/26 11:31:49/

大有用途的扩展函数

不少现代高级编程语言中有扩展函数这个概念,Java 却一直以来都不支持这个非常有用的功
能,这多少会让人有些遗憾。但值得高兴的是,Kotlin 对扩展函数进行了很好的支持,因此这个
知识点是我们无论如何都不能错过的。

首先看一下什么是扩展函数。扩展函数表示即使在不修改某个类的源码的情况下,仍然可以打
开这个类,向该类添加新的函数。

为了帮助你更好地理解,我们先来思考一个功能。一段字符串中可能包含字母、数字和特殊符
号等字符,现在我们希望统计字符串中字母的数量,你要怎么实现这个功能呢?如果按照一般
的编程思维,可能大多数人会很自然地写出如下函数:

object StringUtil { fun lettersCount(str: String): Int { var count = 0 for (char in str) { if (char.isLetter()) { count++ } } return count } 
}

这里先定义了一个StringUtil单例类,然后在这个单例类中定义了一个lettersCount()函
数,该函数接收一个字符串参数。在lettersCount()方法中,我们使用for-in循环去遍历
字符串中的每一个字符。如果该字符是一个字母的话,那么就将计数器加1,最终返回计数器的
值。

现在,当我们需要统计某个字符串中的字母数量时,只需要编写如下代码即可:

val str = "ABC123xyz!@#" 
val count = StringUtil.lettersCount(str)

这种写法绝对可以正常工作,并且这也是Java 编程中最标准的实现思维。但是有了扩展函数之
后就不一样了,我们可以使用一种更加面向对象的思维来实现这个功能,比如说将
lettersCount()函数添加到String类当中。

下面我们先来学习一下定义扩展函数的语法结构,其实非常简单,如下所示:

fun ClassName.methodName(param1: Int, param2: Int): Int { return 0 
}

相比于定义一个普通的函数,定义扩展函数只需要在函数名的前面加上一个ClassName.的语
法结构,就表示将该函数添加到指定类当中了。

了解了定义扩展函数的语法结构,接下来我们就尝试使用扩展函数的方式来优化刚才的统计功
能。

由于我们希望向String类中添加一个扩展函数,因此需要先创建一个String.kt 文件。文件名虽
然并没有固定的要求,但是我建议向哪个类中添加扩展函数,就定义一个同名的Kotlin 文件,这
样便于你以后查找。当然,扩展函数也是可以定义在任何一个现有类当中的,并不一定非要创
建新文件。不过通常来说,最好将它定义成顶层方法,这样可以让扩展函数拥有全局的访问
域。

现在在String.kt 文件中编写如下代码:

fun String.lettersCount(): Int { var count = 0 for (char in this) { if (char.isLetter()) { count++ } } return count 
}

注意这里的代码变化,现在我们将lettersCount()方法定义成了String类的扩展函数,那
么函数中就自动拥有了String实例的上下文。因此lettersCount()函数就不再需要接收一
个字符串参数了,而是直接遍历this即可,因为现在this就代表着字符串本身。

定义好了扩展函数之后,统计某个字符串中的字母数量只需要这样写即可:

val count = "ABC123xyz!@#".lettersCount()

是不是很神奇?看上去就好像是String类中自带了lettersCount()方法一样。

扩展函数在很多情况下可以让API变得更加简洁、丰富,更加面向对象。我们再次以String类
为例,这是一个final类,任何一个类都不可以继承它,也就是说它的API只有固定的那些而
已,至少在Java 中就是如此。然而到了Kotlin 中就不一样了,我们可以向String类中扩展任何
函数,使它的API变得更加丰富。比如,你会发现Kotlin 中的String甚至还有reverse()函数
用于反转字符串,capitalize()函数用于对首字母进行大写,等等,这都是Kotlin 语言自带
的一些扩展函数。这个特性使我们的编程工作可以变得更加简便。

另外,不要被本节的示例内容所局限,除了String类之外,你还可以向任何类中添加扩展函
数,Kotlin 对此基本没有限制。如果你能利用好扩展函数这个功能,将会大幅度地提升你的代码
质量和开发效率。

有趣的运算符重载

运算符重载是Kotlin 提供的一个比较有趣的语法糖。我们知道,Java 中有许多语言内置的运算符
关键字,如+ - * / % ++ --。而Kotlin 允许我们将所有的运算符甚至其他的关键字进行重
载,从而拓展这些运算符和关键字的用法。

本小节的内容相比于之前所学的Kotlin 知识会相对复杂一些,但是我向你保证,这是一节非常有
趣的内容,掌握之后你一定会受益良多。

我们先来回顾一下运算符的基本用法。相信每个人都使用过加减乘除这种四则运算符。在编程
语言里面,两个数字相加表示求这两个数字之和,两个字符串相加表示对这两个字符串进行拼
接,这种基本用法相信接触过编程的人都明白。但是Kotlin 的运算符重载却允许我们让任意两个
对象进行相加,或者是进行更多其他的运算操作。

当然,虽然Kotlin 赋予了我们这种能力,在实际编程的时候也要考虑逻辑的合理性。比如说,让
两个Student对象相加好像并没有什么意义,但是让两个Money对象相加就变得有意义了,因
为钱是可以相加的。

那么接下来,我们首先学习一下运算符重载的基本语法,然后再来实现让两个Money对象相加
的功能。

运算符重载使用的是operator关键字,只要在指定函数的前面加上operator关键字,就可以
实现运算符重载的功能了。但问题在于这个指定函数是什么?这是运算符重载里面比较复杂的
一个问题,因为不同的运算符对应的重载函数也是不同的。比如说加号运算符对应的是plus()
函数,减号运算符对应的是minus()函数。

我们这里还是以加号运算符为例,如果想要实现让两个对象相加的功能,那么它的语法结构如
下:

class Obj { operator fun plus(obj: Obj): Obj { // 处理相加的逻辑} 
}

在上述语法结构中,关键字operator和函数名plus都是固定不变的,而接收的参数和函数返
回值可以根据你的逻辑自行设定。那么上述代码就表示一个Obj对象可以与另一个Obj对象相
加,最终返回一个新的Obj对象。对应的调用方式如下:

val obj1 = Obj() 
val obj2 = Obj() 
val obj3 = obj1 + obj2

这种obj1 + obj2的语法看上去好像很神奇,但其实这就是Kotlin 给我们提供的一种语法糖,
它会在编译的时候被转换成obj1.plus(obj2)的调用方式。

了解了运算符重载的基本语法之后,下面我们开始实现一个更加有意义功能:让两个Money对
象相加。

首先定义Money类的结构,这里我准备让Money的主构造函数接收一个value参数,用于表示
钱的金额。创建Money .kt 文件,代码如下所示:

class Money(val value: Int)

定义好了Money类的结构,接下来我们就使用运算符重载来实现让两个Money对象相加的功
能:

class Money(val value: Int) { operator fun plus(money: Money): Money { val sum = value + money.value return Money(sum) } 
} 

可以看到,这里使用了operator关键字来修饰plus()函数,这是必不可少的。在plus()函
数中,我们将当前Money对象的value和参数传入的Money对象的value相加,然后将得到的
和传给一个新的Money对象并将该对象返回。这样两个Money对象就可以相加了,就是这么简
单。

现在我们可以使用如下代码来对刚刚编写的功能进行测试:

val money1 = Money(5) 
val money2 = Money(10) 
val money3 = money1 + money2 
println(money3.value) 

最终打印的结果一定是15

但是,Money对象只允许和另一个Money对象相加,有没有觉得这样不够方便呢?或许你会觉
得,如果Money对象能够直接和数字相加的话,就更好了。这个功能当然也是可以实现的,因
为Kotlin 允许我们对同一个运算符进行多重重载,代码如下所示:

class Money(val value: Int) { operator fun plus(money: Money): Money { val sum = value + money.value return Money(sum) } operator fun plus(newValue: Int): Money { val sum = value + newValue return Money(sum) } 
}

http://www.ppmy.cn/embedded/167250.html

相关文章

宿主机的 root 是否等于 Docker 容器的 root?

在 Docker 容器化技术中,宿主机的 root 和 容器的 root 并不完全相同,尽管它们都称作 “root 用户”。这里需要明确的是,Docker 容器与宿主机之间存在隔离机制,容器内的 root 用户和宿主机的 root 用户有一些关键的区别。 1. 宿主…

MongoDB私人学习笔记

俗话说“好记性不如烂笔头”,编程的海洋如此的浩大,养成做笔记的习惯是成功的一步! 此笔记主要是ZooKeeper3.4.9版本的笔记,并且笔记都是博主自己一字一字编写和记录,有错误的地方欢迎大家指正。 一、基础知识&#xf…

【Linux网络编程】 HTTP协议

目录 前言 URL 协议格式 常见的方法 状态码 cookie sessionid token 总结 HTTP协议是基于TCP的应用层协议,虽然我们说, 应用层协议是我们程序猿自己定的,但是自己定协议也是比较麻烦要解决两个问题: 序列化与反序列化数据粘包问题…

Java入门——猜测数字游戏

题目: 程序随机给出一个1-1000的整数,然后让你猜是什么数。你可以猜任何数字,游戏会提示过大或过小,从而缩小结果范围。经过几次猜测和提示,终于给出了答案。在游戏过程中,记录游戏结束时需要猜对的次数&a…

自动化反编译微信小程序工具-e0e1-wx

一、项目地址 https://github.com/eeeeeeeeee-code/e0e1-wx 二、简介 1.还在一个个反编译小程序吗?2.还在自己一个个注入hook吗?3.还在一个个查看找接口、查找泄露吗?现在有自动化辅助渗透脚本了,自动化辅助反编译、自动化注入…

绩效管理与业务流程

绩效管理本质就是价值管理,或者说是能力管理,也就是通过一系列的科技手段去发现、证明一个人的能力和价值,然后给予科学、合理的利益分配。业务流程就是把企业的每一个零部件或者说齿轮都有效组合起来形成一个有机体为市场提供自己的独特价值…

封装响应体、自定义异常、全局异常处理、工具类返回响应体

异常设置 创建自己的异常继承运行异常 使用 构造函数 接收错误信息 和 错误code代码 理解: 在运行时 用户输入错误 所以要继承运行异常 断言类 抛出异常 assert a 1 那么 xxx throwIf 创建断言工具类 工具类 方法接收 布尔类型(判断) 运…

ubuntu安装docker docker/DockerHub 国内镜像源/加速列表【持续更新】

ubuntu安装docker & docker镜像代理【持续更新】 在Ubuntu上安装Docker,你可以选择两种主要方法:从Ubuntu的仓库安装,或者使用Docker的官方仓库安装。下面我会详细解释这两种方法。 方法一:从Ubuntu的仓库安装Docker 这种方…