提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方,欢迎各位在评论中指出。
一、扩展函数
扩展函数是Kotlin中一种特殊的函数,它允许我们在不修改原有类的基础上,为该类添加新的方法。这就像是在原有类上“扩展”新的行为或功能。
1.1 大有用途的扩展函数
假设有这么一个需求,你需要统计一个字符串中有多少个字母。以下是常规的写法:
//单例类
object StringUtil {
//字母计数的方法
fun lettersCount(str: String): Int {
var count = 0
for (char in str) {
if (char.isLetter()) {
count++
}
}
return count
}
}
可以看到,我们首先创建了一个StringUtil单例类,然后在里面定义了一个lettersCount()方法,这样就可以在其他地方通过StringUtil.lettersCount()这种方式使用这个方法。然后我们声明了一个变量count用来统计字母的数量,使用for-in循环去遍历字符串。若当前元素是字母则count增加1,在遍历完字符串后返回count的值即可。下面是lettersCount()方法的使用示例:
val str = "sJdasb23s16@!?"
StringUtil.lettersCount(str)
虽然这样也可以实现我们的需求,但运用扩展函数会增强你面向对象编程的思维。首先学习一下如何定义一个扩展函数:
指定类 方法名
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
相比于定义一个普通函数,定义扩展函数只需要在方法名前面加上类名即可表示将该函数添加到指定的类当中。接下来,我们尝试使用扩展函数的方式来实现统计字母的功能。由于我们希望在String类中添加一个扩展函数,那么我们最好定义一个与String类同名的Kotlin文件。并且,最好将扩展函数定义成顶层方法,因为这样全局都能使用这个扩展函数。String.kt文件代码如下:
//注意:lettersCount()没有接收任何参数
fun String.lettersCount(): Int {
var count = 0
//注意:this的使用
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
这里你可能会有几个疑问,为什么lettersCount()方法没有接收任何参数?为什么for-in循环遍历语句用的是this?这是由于我们将lettersCount()方法定义成String类的扩展函数,这种情况下,this关键字指的是调用该扩展函数的实例。下面是lettersCount()扩展函数的使用方法:
val count = "sJdasb23s16@!?".lettersCount()
这样看起来像是String类自带了lettersCount()方法一样,并且这个字符串会自动作为该方法的上下文。在Kotlin中String类还有reversed()函数用来反转字符串、capitalize()函数用来对首字母进行大写,这些都是Kotlin语言自带的扩展函数。
二、运算符的重载
运算符重载是Kotlin提供的一个比较有趣的语法糖,Koltin允许我们通过重载的方式,自定义所有的运算符、关键字。我们甚至可以通过运算符的重载实现对象与对象的运算!
重载是指在一个类中定义多个方法,这些方法的名字相同但参数列表不同。重写则是在子类中重新定义一个与父类中同名、同参数列表的方法。
运算符的重载使用的是operator关键字,只要在特定的方法前面加上operator关键字就可以实现运算符重载的功能了。
2.1 有趣的运算符的重载
下面我们来看一下如何通过运算符重载实现两个Money对象的相加功能 。我们定义首先Money类的结构,这里我们让Money类接收一个Int类型的参数用于代表钱的金额。创建Money.kt文件,使用运算符重载实现Money对象的相加功能:
value代表钱的金额
class Money(val value: Int) {
Money类型参数
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
}
可以看到我们用operator关键字来修饰plus()方法,让其实现运算符的重载。下面是plus()方法的使用示例:
val money1 = Money(5)
val money2 = Money(10)
val sum = money1 + money2 //在编译时会被转换为 val sum = money1.plus(money2)
——>结果 sum = 15
虽然看起来像是两个Money对象直接通过+实现相加运算,实际上Kotlin在编译时会将money1+money2转换成money1.plus(money2)这样的调用方式。Kotlin允许我们对同一个运算符进行多重重载,我们可以通过多重重载让Money对象直接和数字相加:
class Money(val value: Int) {
Money类型参数
operator fun plus(money: Money): Money {
· · ·
}
Int类型参数
operator fun plus(moneyValue: Int): Money {
val sum = value + moneyValue
return Money(sum)
}
}
现在Money对象可以和数字相加了:
val money1 = Money(10)
val result = money1 + 10
——>结果 result = 20
前面你已经知道了,虽然看起来是通过a + b进行运算的,但实际上Kotlin在编译时会将其转化为a.plus(b)这样的调用方式。下面这张表列出了运算符和实际调用的函数之间的关系:
如果我们想判断"hello"字符串中是否包含"he"子串时,首先可以这么写:
if ("hello".contains("he")) {
//code logical
}
而借助重载的语法糖表达式,我们也可以这样写:
if ("he" in "hello") {
//code logical
}
还记得在之前的文章中,我们经常使用一个用于随机生成字符串长度的方法吗?它原来的代码是这样的:
fun getRandomLengthString(str: String): String {
//生成一个1~20之间的随机数n
val n = (1..20).random()
//StringBuilder对象
val builder = StringBuilder()
//重复执行n次
repeat(n) {
builder.append(str)
}
return builder.toString()
}
这个方法是将字符串通过repeat()方法重复n次,来生成字符串。如果我们能实现str * n这样的写法来让str字符串重复n次,是不是很棒呢?我们的思路是,如果想让一个String类型的字符串与Int类型的整数相乘,那么肯定要在String类中重载乘法运算法才行。但String类是系统提供的类,我们无法对其进行修改。这时就可以借助之前所学的扩展函数来向String类中添加新的函数了。
既然是向String类中添加扩展函数,那么我们还是要打开刚才的String.kt文件,添加一个times扩展函数:
乘号的重载 扩展函数
operator fun String.times(n: Int): String {
val builder = StringBuilder()
repeat(n) {
builder.append(this)
}
return builder.toString()
}
现在,我们可以让String类型的字符串与Int类型的整数相乘了:
val str = "abc" * 3 //在编译时会被转换为 "abc".times(3)
——>结果 str = "abcabcabc"
这样getRandomLengthString()方法可以写成如下形式:
fun getRandomLengthString(str: String): String = str * (1..20).random()
另外,Kotlin其实已经提供了一个用于将字符串重复n遍的repeat()函数了。times()函数还可以进一步精简成如下形式:
operator fun String.times(n: Int) = repeat(n)