Kotlin:扩展函数和运算符重载

Kotlin:扩展函数和运算符重载,第1张

Kotlin:扩展函数和运算符重载 Kotlin:扩展函数和运算符重载 1 扩展函数

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

比如,一段字符串中可能包含字母、数字和特殊符号等字符,现在希望统计字符串中字母的数量,要怎么实现这个功能呢?如果按照一般
的编程思维:

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.letterCount(): Int {
  var count = 0
  for (char in this) {
    if (char.isLetter()) {
      count++
    }
  }
  return count
}

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

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

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

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

除了String类之外,还可以向任何类中添加扩展函数,Kotlin对此基本没有限制。

2 运算符重载

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

先来回顾一下运算符的基本用法。在编程语言里面,两个数字相加表示求这两个数字之和,两个字符串相加表示对这两个字符串进行拼 接。但是Kotlin的运算符重载却允许我们让任意两个对象进行相加,或者是进行更多其他的运算 *** 作。

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

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

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

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

val obj1 = Object()
val obj2 = Object()
val obje3 = 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)
  }

}

这里又重载了一个plus()函数,不过这次接收的参数是一个整型数字,其他代码基本是一样的。

那么现在,Money对象就拥有了和数字相加的能力:

val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
val money4 = money3 + 20
println(money4.value) // 35

这里让money3对象再加上20的金额,最终打印的结果就变成了35。

实际上Kotlin允许我们重载的运算符和关键字多达十几个。以下是语法糖表达式和实际调用函数对照表:

注意,最后一个a in b的语法糖表达式对应的实际调用函数是b.contains(a),a、b对象的顺序是反过来的。这在语义上很好理解,因为a in b表示判断a是否在b当中,而b.contains(a)表示判断b是否包含a,因此这两种表达方式是等价的。举个例子,Kotlin中的String类就对contains()函数进行了重载,因此当判断hello字符串中是否包含he子串时,首先可以这样写:

if ("hello".contains("he")) {
}

而借助重载的语法糖表达式,也可以这样写:

if ("he" in "hello") {
}

这两种写法的效果是一模一样的,但后者显得更加精简一些。

再比如,以下是使用了一个随机生成字符串长度的函数,代码如下所示:

fun getRandomLengthString(str: String): String {
  val n = (1..20).random()
  val builder = StringBuilder()
  repeat(n) {
    builder.append(str)
  }
  return builder.toString()
}

其实,这个函数的核心思想就是将传入的字符串重复n次,如果我们能够使用str * n这种写法 来表示让str字符串重复n次,这种语法体验是不是非常棒呢?而在Kotlin中这是可以实现的。

先来说一下思路吧:要让一个字符串可以乘以一个数字,那么要在String类中重载乘号运算符才行,但是String类是系统提供的类,我们无法修改这个类的代码。这个时候就可以借助扩展函数功能向String类中添加新函数了。

在String.kt文件,然后加入如下代码:

operator fun String.times(n: Int): String {
    val builder = StringBuilder()
    repeat(n) {
        builder.append(this)
    }
    return builder.toString()
}

首先,operator关键字肯定是必不可少的;然后既然是要重载乘号运算符,参考上表可知,函数名必须是times;最后,由于是定义扩展函数,因此还要在方向名前面加上String.的语法结构。 在times()函数中,借助StringBuilder和repeat函数将字符串重复n次,最终将结果返回。

现在,字符串就拥有了和一个数字相乘的能力,比如执行如下代码:

val str = "abc" * 3
println(str) // abcabcabc

另外,必须说明的是,其实Kotlin的String类中已经提供了一个用于将字符串重复n遍的repeat()函数,因此times()函数还可以进一步精简成如下形式:

operator fun String.times(n: Int) = repeat(n)

在getRandomLengthString()函数中使用这种魔术一般的写法了,代码如下所示:

fun getRandomLengthString(str: String) = str * (1..20).random()

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5693467.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存