Kotlin笔记--运算符重载

运算符重载

1 重载算术运算符
       Kotlin可以通过扩展函数的机制为现有的类增添新的方法。可以把任意约定方法定义为扩展函数,从而适应任何现有的Java类而不用修改其代码。

.1重载二元算术运算
  定义一个 plus 运算符

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
}

fun main(args: Array) {
    val p1 = Point(10, 20)
    val p2 = Point(30, 40)
    println(p1 + p2)
}

        注意,如何使用 operator 关键字来声明 plus 函数。用于重载运算符的所有函数都需要用该关键字标记,用来表示你打算把这个函数作为相应的约定的实现,并且不是碰巧地定义一个同名函数。在使用了 operator 修饰符声明了 plus 函数之后,你就可以直接使用+号来求和了。事实上,这里它调用的是 plus 函数 。如下:

a + b --->  a.plus(b)

除了把这个运算符声明为一个成员函数外, 也可以把它定义为一个扩展函数。把运算符定义为扩展函数,如下:

operator fun Point.plus(other: Point): Point {
   return Point(x + other.x, y + other .y)
}

        这样实现是一样的。后续的示例中将会使用扩展函数的语法来写,这是给第三方库的类定义约定扩展函数的常用模式。而且 , 对于你自己的类这种语法也同样适用。

可重载的二元算术运算符:

a*b     函数名: times
a/b             div
a%b              mod
a+b             plus 
a-b             minus

运算符 *,/和%具有相同的优先级,高于+和 - 运算符的优先级。

定义一个运算数类型不同的运算符

data class Point(val x: Int, val y: Int)

operator fun Point.times(scale: Double): Point {
    return Point((x * scale).toInt(), (y * scale).toInt())
}

fun main(args: Array) {
    val p = Point(10, 20)
    println(p * 1.5)
}

   注意 : Kotlin 运算符不会自动支持交换性 (交换运算符的左右两边)。如果希望用户能够使用 1.5 * p 以外, 还能使用 p * 1.5 ,你需要为它定义一个单独的运算符 : operator fun Double. times (p : Point): Point 。

2 重载复合赋值运算符

       通常情况下,当你在定义像 plus 这样的运算符函数时, Kotlin 不止支持+ 号运算 ,也支持+= 。 像+=、-=等这些运算符被称为复合赋佳运算符。看这个例子:

>> var point= Point(l, 2)
>> point+= Point(3 , 4)
>> println(point)
Point(x=4 , y=6)

这等同于 point= point + Point( 3 , 的的写法。当然 ,这个只对于可变变量有效。

        在一些情况下,定义+=运算可以修改使用它的变量所引用的对象,但不会重新分配引用 。将一个元素添加到可变集合,就是一个很好的例子 :

>> val numbers = ArrayList ()
>> numbers += 42
>> println(numbers[O])
42

         如果你定义了一个返回值为 Unit,名为 plusAssign 的函数, Kotlin 将会在用到+=运算符的地方调用它。其他二元算术运算符也有命名相似的对应函数:如minusAssign 、 timesAssign 等 。

3 重载一元运算符

       重载一元运算符的过程与你在前面看到的方式相同:用预先定义的一个名称来声明函数(成员函数或扩展函数),并用修饰符 operator 标记。我们来看一个例子 。

data class Point(val x: Int, val y: Int)

operator fun Point.unaryMinus(): Point {
    return Point(-x, -y)
}

fun main(args: Array) {
    val p = Point(10, 20)
    println(-p)
}

4 重载比较运算符

     如果在 Kotiin 中使用==运算符,它将被转换成 equals 方法的调用。这只是我们要讨论的约定原则中的一个。
     使用 ! =运算符也会被转换成 equals 函数的调用,明显的差异在于,它们的结果是相反的。注意,和所有其他运算符不同的是,==和!=可以用于可空运算数,因为这些运算符事实上会检查运算数是否为 null 。比较 a == b 会检查 a 是否为非空,如果不是,就调用 a . equals (b ) (如图 7.4 所示);否则,只有两个参数都是空引用,结果才是 true 。

a == b -->  a?.equals(b) ?: (b == null)

等式校验 == 被转换为 equals 函数的调用 , 以及 null 的校验

5 集合与区间的约定

        处理集合最常见的一些操作是通过下标来获取和设置元素,以及检查元素是否属于当前集合。所有的这些操作都支持运算符语法:要通过下标获取或设置元素 ,可以使用语法 a [b] (称为下标运算符) 。 可以使用 in 运算符来检查元素是否在集合或区间内,也可以迭代集合。可以作为集合的自定义类。让我们来看看用于支持这些操作的约定。

5.1 通过下标来访问元素:“get”和“set”

在 Kotiin 中,可以用类似 Java 中数组的方式来访问 map 中的元素一一使用方括号:

val value = map [key]

也可以用同样的运算符来改变一个可变 map 的元素:

mutableMap[key) = newValue

        来看看它是如何工作的。在 Kotlin 中,下标运算符是一个约定。使用下标运算符读取元素会被转换为 get 运算符方法的调用 ,井且写入元素将调用 set 。 Map 和MutableMap 的接口己经定义了这些方法。让我们看看如何给自定义的类添加类似的方法。

data class Point(val x: Int, val y: Int)

operator fun Point.get(index: Int): Int {
    return when(index) {
        0 -> x
        1 -> y
        else ->
            throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main(args: Array) {
    val p = Point(10, 20)
    println(p[1])
}

输出: 20

      你只需要定义一个名为 get 的函数,并标记 operator。之后,像 p [1]这样的表达式,其中 p 具有类型 Point ,将被转换为 get 方法的调用,

x[a.b] --> x.get(a,b)

     方括号的访问会被转换为get函数的调用注意, get 的参数可以是任何类型,而不只是 Int 。例如,当你对 map 使用下标运算符时,参数类型是键的类型,它可以是任意类型。还可以定义具有多个参数的 get 方法。例如,如果要实现一个类来表示二维数组或矩阵,你可以定义一个方法,例如 operator fu口 get (rowindex: Int, colindex: Int ),然后用matrix [row,col ]来调用。如果需要使用不同的键类型访问集合,也可以使用不同的参数类型定义多个重载的 get 方法。

     我们也可以用类似的方式定义一个函数,这样就可以使用方括号语法更改给定下标处的值。 Point 类是不可变的,所以定义 Poin t 的这种方法是没有意义的 。作为例子,我们来定义另一个类来表示一个可变的点。

data class MutablePoint(var x: Int, var y: Int)

operator fun MutablePoint.set(index: Int, value: Int) {
    when(index) {
        0 -> x = value
        1 -> y = value
        else ->
            throw IndexOutOfBoundsException("Invalid coordinate $index")
    }
}

fun main(args: Array) {
    val p = MutablePoint(10, 20)
    p[1] = 42
    println(p)
}
输出:
MutablePoint(x=lO, y=42)

6  “in”的约定

       集合支持的另一个运算符是川运算符,用于检查某个对象是否属于集合 。 相应的函数叫作 contains 。我们来实现一下,使用 in 运算符来检查点是否属于一个矩形 。栗子:

data class Point(val x: Int, val y: Int)

data class Rectangle(val upperLeft: Point, val lowerRight: Point)

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x &&
           p.y in upperLeft.y until lowerRight.y
}

fun main(args: Array) {
    val rect = Rectangle(Point(10, 20), Point(50, 50))
    println(Point(20, 30) in rect)
    println(Point(5, 5) in rect)
}

 分析:in 右边的对象将会调用 cont a ins 函数, in 左边的对象将会作为函数入参 。在 Rectangle.contains 的实现中,我们用到了的标准库的 until 函数,来构建一个开区间,然后使用运算符 in 来检查某个点是否属于这个区间。

a in c  --->  c.contains(a)  in 操作将会转换为 contains 函数的调用

7 rangeTo 的约定
       要创建一个区间,请使用 ..语法:举个例子, 1. . 10 代表所有从 1 到 10 的数字。在前面你己经看到过区间的使用,现在让我们来研究一下创建它的约定。 .. 运算符是调用 range To 函数的一个简洁方法

start..end  --->  start.rangeTo(end)  ..运算符将被转换为 rangeTo 函数的调用

      range To 函数返回一个区间。你可以为自己的类定义这个运算符。但是,如果该类实现了 Comparable 接口,那么不需要了 : 你可以通过 Kotiin 标准库创建一个任意可比较元素的区间,这个库定义了可 以用 于任何可比较元素的 range To 函数:

operator fun > T.rangeTo(that: T): ClosedRange
  这个函数返回一个区间,可以用来检测其他一些元素是否属于它。

8 解构声明和组件函数
 解构声明。这个功能允许你展开单个复合值,并使用它来初始化多个单独的变量。

val p = Point(10,20)
  val(x,y) = p
  println(x)
  10
  println(y)
  20

        一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量。事实上,解构声明再次用到了约定的原理。要在解构声明中初始化每个变量,将调用名为 componentN 的函数,其中 N 是声明中变量的位置。换句话说如下:

                  -- val a = p.component1()
val(a,b) = p --- |
                  -- val b = p.component2()

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

 class Point(val x: Int, val y :Int) {
  operator fun cornponentl() = x
  operator fun cornponent2() = y
}

       解构声明主要使用场景之一,是从一个函数返回多个值,这个非常有用。如果要这样做,可以定义一个数据类来保存返回所需的值,并将它作为函数的返回类型。在调用函数后,可以用解构声明的方式,来轻松地展开它,使用其中的值。举个例子,让我们编写一个简单的函数,来将一个文件名分割成名字和扩展名。

data class NameComponents(
    val name: String,
    val extension: String)

fun splitFilename(fullName: String): NameComponents {
    val (name, extension) = fullName.split('.', limit = 2)
    return NameComponents(name, extension)
}

输出:
example
kt

    解构声明和循环
    解构声明不仅可以用作函数中的顶层语句,还可以用在其他可以声明变量的地方,例如 in 循环。一个很好的例子,是枚举 map 中的条目 。 下面是一个小例子,

fun printEntries(map: Map) {
    for ((key, value) in map) {
        println("$key -> $value")
    }
}

fun main(args: Array) {
    val map = mapOf("Oracle" to "Java", "JetBrains" to "Kotlin")
    printEntries(map)
}

这个简单的例子用到了两个 Kotlin 约定 : 一个是迭代一个对象,另 一个是用于解构声明。 Kotiin 标准库给 m叩增加了一个扩展的 iterator 函数,用来返回map条目的法代器。因此,与 Java 不同的是,可以直接送代 map。它还包含 Map.Entry 上的扩展函数 componentl 和 component2 ,分别返回它的键和值。实际上,前面的循环被转换成了这样的代码:

for (entry in map.entries) {
val key = entry. cornponentl ()
val value= entry.cornponent2()
//. . .
}

 

你可能感兴趣的:(Android基础)