Kotlin学习(十七): 运算符重载
来源:互联网 发布:管碧玲 知乎 编辑:程序博客网 时间:2024/06/08 07:53
在前面写了关于集合和范围的内容,里面包括了一点运算符重载的内容,在这里我们来详细了解运算符重载的知识,内容参考《Kotlin实战》(Kotlin in Action)。
什么是运算符重载?
简单来说,就是Kotlin通过调用自己代码中定义特定的函数名的函数(成员函数或者扩展函数),并且用operator
修饰符标记,来实现特定的语言结构,例如如果你在一个类上面定义了一个特定函数命名plus
的函数,那么按照Kotlin的约定,可用在这个类的实例上使用+
运算符,下面是代码。
用于重载运算符的所有函数都必须使用
operator
关键字标记。
// 一个简单的数据类data class Foo(val x: Int, val y: Int) { operator fun plus(other: Foo) : Foo = Foo(x + other.x, y + other.y)}fun main(args: Array<String>) { // 使用的时候 val f1 = Foo(10, 20) val f2 = Foo(30, 40) // 直接用+运算符代替plus函数,事实上会调用plus函数 println(f1 + f2) // 打印内容为Foo(x=40, y=60)}
那么Java如何调用运算符函数呢?
重载的运算符实际上是被定义成一个函数,Java调用Kotlin运算符就跟调用普通函数一样调用就行。
重载算术运算符
算术运算符包括二元运算符、复合赋值运算符、一元运算符,当Kotlin在给一个集合添加元素的时候,是调用add方法,用到重载的话,我们就可以直接用+=
来进行这个操作,就会显得更加的优雅。。。
fun Any.println() = println(this)fun main(args: Array<String>) { val list = arrayListOf(1, 2 ,3) list.println() // 打印[1, 2, 3] list.add(4) list.println() // 打印[1, 2, 3, 4] list += 5 list.println() // 打印[1, 2, 3, 4, 5]}
重载二元算术运算符
二元算术运算符就是常见的+
、-
、*
、/
和取余%
,优先级与数学的是一样的,*
、/
和%
要高于+
、-
的优先级。
下面我们列举对应的函数名:
a * b
times
a / b
div
a % b
rem
,mod(弃用)
a + b
plus
a - b
minus
下面我们来写个类,里面包含这几种函数,同时还有扩展函数的定义。
fun Any.println() = println(this)// Extensionoperator fun Foo.minus(other: Foo): Foo = Foo(x - other.x, y - other.y)operator fun Foo.div(other: Foo): Foo = Foo(x / other.x, y / other.y)data class Foo(val x: Int, val y: Int) { operator fun plus(other: Foo): Foo = Foo(x + other.x, y + other.y) operator fun times(other: Foo): Foo = Foo(x * other.x, y * other.y) operator fun rem(other: Foo): Foo = Foo(x % other.x, y % other.y)}fun main(args: Array<String>) { val f1 = Foo(30, 40) val f2 = Foo(10, 20) (f1 - f2).println() // 打印Foo(x=20, y=20) (f1 + f2).println() // 打印Foo(x=40, y=60) (f1 * f2).println() // 打印Foo(x=300, y=800) (f1 / f2).println() // 打印Foo(x=3, y=2) (f1 % f2).println() // 打印Foo(x=0, y=0)}
除了定义相同类型的运算数之外,还能定义运算数类型不同的运算符:
data class Foo(val x: Int, val y: Int) { operator fun times(other: Double): Foo = Foo((x * other).toInt(), (y * other).toInt())}fun main(args: Array<String>) { val f1 = Foo(30, 40) (f1 * 1.5).println() // 打印Foo(x=45, y=60)}
当你通过这样子去调用这个运算符的时候
(1.5 * f1).println()
这时候,编译器会提示你出错了
为什么会这样呢?
因为Kotlin的运算符不会自动至此交换性(交换运算符的左右两边)。
那要怎么样才能那样写呢?
需要定义一个单独的运算符
operator fun Double.times(other: Foo): Foo = Foo((this * other.x).toInt(), (this * other.y).toInt())
这样子就能直接支持运算符两边互换使用了。。。
(f1 * 1.5).println()(1.5 * f1).println()
运算符函数不是单一返回类型的,也是可以定义不同的返回类型,下面举个栗子:
operator fun Char.times(count: Int): String = toString().repeat(count)fun main(args: Array<String>) { ('a' * 3).println() // 打印aaa}
在上面的代码中,这个运算符是Char
类型的扩展函数,参数类型是Int
类型,所以是Char * Int
这样的操作,返回类型是String
。
注意:运算符和普通函数一样,可以重载operator函数,可以定义多个同名,但是参数不一样的方法。
重载复合赋值运算符
什么是复合赋值运算符?
类似于+=
这样的,合并了两部操作的运算符,同时赋值,称为符合运算符。
下面我们列举对应的函数名:
a += b
timesAssign
a /= b
divAssign
a %= b
remAssign
a += b
plusAssign
a -= b
minusAssign
fun main(args: Array<String>) { var f1 = Foo(1, 2) f1 += Foo(3, 4) f1.println() // 打印Foo(x=4, y=6)}
上面的+=
等同于f1 = f1 + Foo(3, 4)
,这些操作当然是只对可变变量有效的。
默认情况下,复合赋值运算符是可以修改变量所引用的对象,同时重新分配引用,但是在将一个元素添加到一个可变集合的时候,+=
是不会重新分配引用的:
fun main(args: Array<String>) { val list = mutableListOf<Int>() list += 42 list.println() // 打印[42]}
同样我们可以对复合赋值运算符进行重载,同样可以定义多个同名,但是参数不一样的方法:
operator fun MutableCollection<Int>.plusAssign(element: Int) { this.add(element - 1)}fun main(args: Array<String>) { val list = mutableListOf<Int>() list += 42 list.println() // 打印[41]}
如果在plus
和plusAssign
两个函数同时被定义且适用,那么编译器就会报错,最好在设计新类的时候保持(可变性)一致,尽量不同时定义plus
和plusAssign
运算。如Foo
类是不可变的,那么只提供plus
运算,如果一个类是可变的,如构造器,那么只需提供plusAssign
和类似的运算就够了。
实际上+=
可以被转换为plus
或者plusAssign
函数调用,而Kotlin的标准库中为集合支持这两种方法。
- +
和-
运算符会返回一个新的集合。
- +=
和-=
用于可变集合,会修改集合,如果是只读,那么就会返回一个修改过的副本,也就是说只有在只读集合被定义为var类型的时候,才能使用+=
和-=
。
fun main(args: Array<String>) { // 可变类型 val list = mutableListOf<Int>(1, 2) // += 修改list list += 3 // + 返回一个新的List val newList = list + listOf<Int>(4, 5) // 除了使用单个元素参数,也可使用元素类型相同的集合 list.println() // 打印[1, 2, 3] newList.println() // 打印[1, 2, 3, 4, 5] var varList = listOf<Int>(1, 2) // 只读集合类型为var varList.println() // 打印[1, 2] varList += 3 varList.println() // 打印[1, 2, 3]}
重载一元运算符
Kotlin中允许重载一元运算符,如-a
,+a
等等,同样我们列举支持的一元运算符和对应的函数名:
+a
unaryPlus
-a
unaryMinus
!a
not
++a, a++
inc
--a, a--
dec
重载一元运算符过程与前面一样,通过预先定义的一个名称来声明函数(成员函数或者扩展函数),并且用operator
修饰符标记。
注意:一元运算符是没有参数的。
data class Foo(val x: Int, val y: Int)operator fun Foo.unaryMinus() = Foo(-x, -y)fun main(args: Array<String>) { val f1 = Foo(1, 2) (-f1).println() // 打印Foo(x=-1, y=-2)}
当重载自增自减运算符符是,编译器自动支持前缀--a
和后缀a--
语义。
operator fun BigDecimal.inc() = this + BigDecimal.ONEfun main(args: Array<String>) { var bd = 0 (bd++).println() // 打印0 (++bd).println() // 打印2}
重载比较运算符
比较运算符,可以在除了基本数据类型外的任意对象上使用,当Java中使用equals
或compareTo
时,在Kotlin中,直接用运算符重载。
比较运算符分为等号运算符和排序运算符。
a == b
a?.equals(b) ?: (b === null)
a != b
!(a?.equals(b) ?: (b === null))
a > b
a.compareTo(b) > 0
a < b
a.compareTo(b) < 0
a >= b
a.compareTo(b) >= 0
a <= b
a.compareTo(b) <= 0
等号运算符equals
在我们平时使用判断字符串是否与某个字符串相等的时候,会使用equals
函数来判断,然而在Kotlin中,我们可以是用==
来代替equals
函数,~=
来代替!qeuals
。
在Java中如果使用null对象来equals
的话,会爆空指针异常,而Kotlin中的==
是支持可空类型的,因为会先判断是否为空,如a == b
会先检查a
是否为空,如果不是,就会调用a.equals(b)
,否则只有两个参数都是空值,结果才为真。
下面我们来重载equals
运算符
data class Foo(val x: Int, val y: Int) { override operator fun equals(other: Any?): Boolean = when { // 使用恒等运算符来判断两个参数是否同一个对象的引用 other === this -> true other !is Foo -> false else -> other.x == x && other.y == y }}fun main(args: Array<String>) { val f1 = Foo(1, 2) val f2 = Foo(1, 2) val f3 = Foo(10, 20) (f1 == f2).println() // true (f1 == f2).println() // true (f1 != f2).println() // false (null == f1).println() // false}
注意:
===
与Java一样,检查两个参数是否是同一个对象的引用,如果是基本数据类型,检查值是否相同,===
和!==
不能被重载。
排序运算符compareTo
在Java中,基本数据类型集合排序通常都是使用<
和>
来比较,而其他类型需要使用element1.compareTo(element2)
来比较的。而在Kotlin中,通过使用比较运算符(>``<``>=``<=
)来进行比较。
比较运算符会被转换成compareTo
函数,compareTo
的返回类型必须为Int
。
class Person(private val firstName: String, private val lastName: String) : Comparable<Person> { override fun compareTo(other: Person): Int = compareValuesBy(this, other, Person::lastName, Person::firstName)}fun main(args: Array<String>) { (Person("Alice", "Smith") < Person("Bob", "Johnson")).println() // 打印false}
compareValuesBy
函数是按顺序依次调用回调方法,两两一组分别做比较,然后返回结果,如果则返回比较结果,如果相同,则继续调用下一个,如果没有更多回调来调用,则返回0。
override
标记
从上面可以看到,equals
和compareTo
都是被override
标记的,之所以会被标记,是因为在Any
类中已经定义了equals
函数,而所有的对象都默认继承Any
类,所有才重载的时候需要使用override
标记,而且equals
不能定义为扩展函数,因为Any
类的实现是重要优先于扩展函数。
同样,compareTo
在Comparable
接口中已经定义了,所有在重载的时候,需要使用override
标记。
- Kotlin学习(十七): 运算符重载
- Kotlin - 运算符重载
- Kotlin基础教程-运算符重载
- Kotlin运算符重载总结
- Kotlin学习(6)操作符重载
- 十七、运算符重载(一) 成员函数重载、友元函数重载、运算符重载规则
- 运算重载符学习
- 重载运算符学习
- Swift学习这十七:重载(override)
- Swift学习这十七:重载(override)
- Kotlin-31.操作符/运算符重载(operator overload)
- C#学习:运算符重载
- Python运算符重载学习
- 运算符重载 学习笔记
- 运算符重载学习小记
- C++学习-运算符重载
- 运算符重载学习笔记
- C++学习-运算符重载
- 五.从尾到头打印链表
- hdu 2544 最短路
- CentOS65安装Mysql5.7.20
- 基础练习 字母图形
- 轻松解决桌面或者开始菜单里的图标显示异常
- Kotlin学习(十七): 运算符重载
- 718. Maximum Length of Repeated Subarray
- 我们应该经常交流!
- MyBatis config 文件常用设置
- ACM
- Friends(老友记第一季词组)
- pip与wxpython的安装
- 20171119
- hihocoder1158 质数相关