Kotlin 知识梳理(8) 运算符重载及其余约定

1、本文概要

本文是对<<Kotlin in Action>>的学习笔记,若是须要运行相应的代码能够访问在线环境 try.kotlinlang.org,这部分的思惟导图为: java

Kotlin中,咱们能够经过 调用本身代码中定义的函数,来实现 特定语言结构。这些功能与 特定的函数命名 相关,而不是与特定的类型绑定。例如,若是在你的类中定义了一个名为 plus的特殊方法,那么按照约定,就能够在该类的实例上使用 +运算符,这种技术称为 约定

由于由类实现的接口集是固定的,而Kotlin不能为了实现其余接口而修改现有的类,所以通常 经过扩展函数的机制 来为现有的类增添新的 约定方法,从而适应任何现有的Java类。算法

2、重载算术运算符

Kotlin中,使用约定的最直接的例子就是 算术运算符,在Java中,全套的算术运算符只能用于基本数据类型,+运算符能够与String一块儿使用。下面,咱们看一下在Kotlin中,如何使用算术运算符来完成一些其它的事情。数组

2.1 重载二元运算符

假设已经有一个数据类Point,它包含两个成员变量,分别是x,y点的坐标值,咱们但愿经过算术运算符+对两个Point对象相加以后,可以获得一个新的Point对象,它的成员变量x,y为原有两个Point对象的x,y之和。 ide

运行结果为:
在上面的代码中,咱们为 Point类定义了一个扩展函数 plus,这样当咱们调用 first + second,实际上执行的是 first.plus(second)方法来获得一个新的 Point对象。这里须要注意的是:用于重载运算符的全部函数都须要 用 operator 关键字来标记,用来表示你打算 把这个函数做为相应的约定的实现

全部可重载的二元算术运算符以下,自定义类型的运算符,基本上和标准数字类型的运算符有着相同的优先级。函数

  • a * btimes
  • a / bdiv
  • a % bmod
  • a + bplus
  • a - bminus

运算符函数和 Java

  • 当从Java调用Kotlin运算符很是容易,只须要像普通函数同样调用便可,例如上面的plus方法。
  • 当从Kotlin调用Java的时候,对于与Kotlin约定匹配的函数(不要求使用operator修饰符,可是参数须要匹配名称和数量)均可以使用运算符语言来调用。若是Java类定义了一个知足需求的函数,可是起了一个不一样的名称,能够经过定义一个扩展函数来修正这个函数名用来替代现有的Java方法。

没有用于位运算的特殊运算符

Kotlin没有为标准数字类型IntLong等定义任何位运算符,所以也不容许你为自定类型定义它们。相反,它使用中缀调用语法的函数,能够为自定义类型定义类似的函数,下面咱们为Point添加一个and,用于执行位运算。 学习

运行结果为:
这里咱们再也不使用 operator关键字来声明,而是用 infix来定义一个中缀调用语法的函数,其它执行位运算的函数包括: shlshrushrandorxorinv

2.2 重载复合赋值运算符

当在定义像plus这样的函数,Kotlin不止支持+号运算,也支持像+=这样的 复合赋值运算符spa

须要注意,这个只对于可变变量有效,也就是 first要声明为 var。在一些状况下,定义 +=运算符能够 修改使用它的变量所引用的对象,但不会从新分配引用,将一个元素添加到可变集合,就是一个很好的例子:

若是你定义了一个返回值为 Unit,名为 plusAssign的函数, Kotlin将会在用到 +=运算符的地方使用它,其它二元运算符也有命名类似的对应函数: minusAssigntimesAssign等。

当在代码中用到+=的时候,理论上plusplusAssign均可能会被调用,若是两个函数都有定义而且适用,那么编译器就会报错,例以下面这样的定义: 3d

编译时的错误为:
解决方法有两种:

  • 使用 不可变 val 代替可变 var 来修饰first,这样plus运算符就再也不适用。
  • 不要同时为一个类添加plusplusAssign运算。若是一个类是 不可变的,那就应该只提供返回一个新值的运算;若是一个类是 可变的,例如构建器,那么只须要提供plusAssign和相似的运算符就够了。

Kotlin的标准库支持集合的这两种方法:code

  • +-运算符老是返回一个新的集合
  • +=-=运算符用于可变集合时,始终在一个地方修改它们;而它们用于只读集合时,会返回一个修改过的副本。

做为它们的运算数,可使用单个元素,也可使用元素类型一致的其它集合: component

运行结果为:

2.3 重载一元运算符

重载一元运算的过程和前面看到的方式相同:用预先定义的一个名称来声明函数,并用修饰符operator标记。下面的例子中重载了-a运算符:

运行结果为:
全部可重载的一元算法运算符包括:

  • +aunaryPlus
  • -aunaryMinus
  • !anot
  • ++a/a++inc
  • --a/a--dec

当你定义incdec函数来重载自增和自减的运算符时,编译器自动支持与普通数字类型的前缀、后缀自增运算符相同的语义。例如后缀运算会先返回变量的值,而后才执行++操做。

3、重载比较运算符

与算术运算符同样,在Kotlin中,能够对任何对象使用比较运算符(==!=><),而不只仅限于基本数据类型。

3.1 等号运算符,equals

若是在Kotlin中使用==/!=运算符,它将被转换成equals方法的调用,和其余运算符不一样的是,==!=能够用于可空运算数,比较a == b会检查a是否为飞空,若是不是就调用a.equals(b),完整的调用以下所示:

a?.equals(b) ?: (b == null)
复制代码

对于data修饰的数据类,equals的实现将会由编译器自动生成,若是须要手动实现,能够参考下面的作法:

  • 比较是否指向同一对象的引用,若是是,那么直接返回true
  • 类型若是不一样,直接返回false
  • 比较做为判断依据的字段

equals函数之因此被标记为override,这是由于这个方法的实现是在Any类中定义的,而operator关键字在基本方法中已经标记了。同时,equals不能实现为扩展函数,由于继承自Any类的实现始终优先于扩展函数。

3.2 排序运算符 compareTo

Kotlin中,对于实现了Comparable接口中定义的compareTo方法的类能够按约定调用,比较运算符<、>、<=、>=的使用将被转换为compareTocompareTo的返回类型必须为int,也就是说p1 < p2表达式等价于p1.compareTo(p2) < 0

下面,咱们定义一个Person类,让其根据年龄来比较大小:

运行结果为:
在上面的例子中,咱们用到了 Kotlin标准库函数中的 compareValuesBy函数来简洁地实现 compareTo方法,这个函数 接收用来计算比较值的一系列回调,按顺序依次调用回调方法,两两一组分别作比较:

  • 若是值不一样,则返回比较结果
  • 若是相同,则继续调用下一个
  • 若是没有更多的回调来调用,则返回0

这些回调函数能够像lambda同样传递,或者像这里作的同样,做为属性引用传递。

4、集合与区间的约定

处理集合最多见的操做包含两种:

  • 经过下标来获取和设置元素,使用语法a[b],称为 下标运算符
  • 检查元素是否属于当前集合,使用in运算符。

4.1 经过下标来访问元素:get 和 set

Kotlin中,下标运算符是一种约定,使用下标运算符读取元素会被转换为get运算符方法的调用,而且写入元素将调用set,下面咱们为Point类添加相似的方法:

get的参数能够是任何类型,而不止是 Int,例如,当你对 map使用下标运算符时,参数类型是键的类型,它能够是任意类型。还能够定义具备多个参数的 get方法,例如若是要实现一个类来表示二维数组或矩阵,你能够定义一个方法,例如 operator fun get(rowIndex : Int, colIndex : Int),而后用 matrix[row, col]来调用。

下面,咱们再来看一下set的约定方法:

运行结果为:
定义 set函数后,就能够在赋值语句中使用下标运算符, set的最后一个参数用来接收赋值语句中(等号)右边的值,其余参数做为方括号内的下标。

4.2 in 的约定

集合支持的另外一个运算符是in运算符,用于检查某个对象是否属于集合,相应的函数叫作contains,下面的例子用于判断某个点是否处于矩形范围以内:

运行结果为:

4.3 rangeTo 的约定

要建立一个区间时,使用的是..语法,例如1..10表明全部从110的数字,..运算符是调用rangeTo函数的一个简洁方法。rangeTo返回一个区间,你能够为本身的类定义这个运算符,可是,若是该类实现了Comparable接口,那么就不须要了,你能够经过Kotlin标准库建立一个任意可比较元素的区间,这个库定义了能够用于任何可比较元素的rangeTo函数

operator fun <T : Comparable<T>> T.rangeTo(that : T) : ClosedRange<T>
复制代码

这个函数返回一个区间ClosedRanged,能够用来检测其它一些元素是否属于它。

做为例子,咱们用LocalData来构建一个日期的区间:

运行结果为:
上面的 now..now.plusDays(10)将会被编译器转换为 now.rangeTo(now.plusDays(10)),它并非 LocalDate的成员函数,而是 Comparable的一个扩展函数。

4.4 在 "for" 循环中使用 "iterator" 的约定

for循环中使用in运算符表示 执行迭代操做,诸如for(x in list) { }将被转换成list.iterator()的调用,而后在上面重复调用hasNextnext方法。

运行结果为:
上面用到了 Kotlin 知识梳理(4) - 数据类、类委托 及 object 关键字 中介绍的经过 object来实现匿名内部类的知识。

5、解构声明和组件函数

解构声明的功能容许你展开单个复合值,并使用它来初始化多个单独的变量。它再次用到了约定的原理,要在解构声明中初始化每一个变量,将调用名为componentN的函数,其中N是声明中变量的位置。

对于数据类,编译器为每一个在主构造方法中声明的属性生成一个componentN函数,下面的例子显示了如何手动为非数据类声明这些功能:

运行结果为:
解构声明主要使用场景之一,是从一个函数返回多个值,这个很是有用。若是要这样作,能够定义一个数据类来保存返回所需的值,并将它做为函数的返回类型。在调用函数以后,能够用解构声明的方式,来轻松的展开它,使用其中的值。

解构声明不只能够用做函数中的顶层语句,还能够用在其余能够声明变量的地方,例如使用in循环来枚举map中的条目:

运行结果为:


更多文章,欢迎访问个人 Android 知识梳理系列:

相关文章
相关标签/搜索