共计 5642 个字符,预计需要花费 15 分钟才能阅读完成。
扩大函数和运算符重载
不少古代高级编程语言中有扩大函数这个概念,Java 却始终以来都不反对这个十分有用的性能,这多少会让人有些遗憾。但值得快乐的是,Kotlin 对扩大函数进行了很好的反对,因而这个知识点是咱们无论如何都不能错过的。
大有用处的扩大函数
首先看一下什么是扩大函数。扩大函数示意即便在 不批改某个类的源码的状况下,依然能够关上这个类,向该类增加新的函数。
为了帮忙你更好地了解,咱们先来思考一个性能。一段字符串中可能蕴含字母、数字和特殊符号等字符,当初咱们心愿统计字符串中字母的数量,你要怎么实现这个性能呢?如果依照个别的编程思维,可能大多数人会很天然地写出如下函数:
object StringUtil {fun letterCount(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.k t 文件中编写如下代码:
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)
然而,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)
这里让 money3 对象再加上 20 的金额,最终打印的后果就变成了 35。
当然,你还能够对这个例子进一步扩大,比方加上汇率转换的性能。让 1 人民币的 Money 对象和 1 美元的 Money 对象相加,而后依据实时汇率进行转换,从而返回一个新的 Money 对象。这类性能都是十分乏味的,运算符重载如果使用得好的话,能够玩出很多花色。
后面咱们花了很长的篇幅介绍加号运算符重载的用法,但实际上 Kotlin 容许咱们重载的运算符和关键字多达十几个。显然这里我不可能将每一种重载的用法都一一进行介绍,因而我在下表中列出了所有罕用的可重载运算符和关键字对应的语法糖表达式,以及它们会被转换成的理论调用函数。如果你想重载其中某一种运算符或关键字,只有参考方才加号运算符重载的写法去实现就能够了。
语法糖表达式 | 理论调用函数 |
---|---|
a+b | a.plus(b) |
a-b | a.plus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a++ | a.inc() |
a– | a.dec() |
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a == b | a.equals(b) |
a > b | a.equals(b) |
a < b | a.equals(b) |
a >= b | a.equals(b) |
a <= b | a.compareTo(b) |
a..b | a.rangeTo(b) |
a[b] | a.get(b) |
a[b] = c | a.set(b, c) |
a in b | b.contains(a) |
那么对于运算符重载的内容就学到这里。接下来,咱们联合刚刚学习的扩大函数以及运算符重载的常识,对之前编写的一个小性能进行优化。
回忆一下,在第 4 章和本章中,咱们都应用了一个随机生成字符串长度的函数,代码如下所示:
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 类中增加扩大函数,那么咱们还是关上方才创立的 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()
怎么样,有没有感觉这种语法用起来特地难受呢?只有你能灵便应用本节学习的扩大函数和运算符重载,就能够定义出更多乏味且高效的语法结构来,本书在后续章节中也会对这部分性能进行更多的拓展。