Kotlin自学之旅(九)约定和重载运算符

重载运算符

在Kotlin中,可以通过定义特定名字的函数来实现运算符的重载,这种方式被称为约定


算术运算符

使用约定的最直接的例子就是算术运算符:

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

val point1 = Point(1,2)
val point2 = Point(3,4)
println(point1 + point2) //输出:Point(x=4, y=6)

用于重载的函数都需要使用关键字 operator 修饰,像上面这样定义了plus函数后,我们就可以使用 + 号给两个Point求和了,但事实上,这个时候它调用的是plus函数。
除了把这个运算符声明为一个成员函数外,也可以把它定义为一个扩展函数。上面的plus函数和下面这个是等价的:

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

虽然重载函数都需要operator关键字,但并不意味着我们可以用operator重载任意运算符或者用随意名字命名重载函数。事实上, Kotlin 限定了我们能重载哪些运算符,以及需要在类中定义的对应名字的函数。下面这个表列举了我们可以重载的函数以及对应的函数名:

表达式函数名
a * btimes
a / bdiv
a % brem
a + bplus
a - bminus

自定义类型的运算符,基本上和与标准数字类型的运算符有着相同的优先级。例如,如果是 a + b * c,乘法将始终在添加之前执行, 即使是自己重载了这些运算符。运算符 * 、/和%具有相同的优先级,高于+和 - 运算符的优先级。
定义运算符的时候,不要求两个运算符是相同的类型,函数的返回值也可以是任意类型(可以不同于任一运算符参数)。甚至我们可以像对普通函数一样重载多个同名的,但参数类型不同的operator函数。
除了上面已经列举的运算符,Kotlin还支持一元运算符,和复合赋值运算符,下面是这些运算符的列表:

表达式函数名
+aunaryPlus
-aunaryMinus
!anot
a++inc
a- -dec
a += bplusAssign
a -= bminusAssign
a *= btimesAssign
a /= bdivAssign
a %= bremAssign

比较运算符

和算法运算符一样,我们同样可以重载比较运算符。


等号运算符

我们可以重载等号运算符使得对任何对象都能使用 ==!= ,重载这两个运算符,我们只需要定义一个equals方法—— == 会调用equals方法,而 != 则会调用equals方法并返回方法结果的相反值。与算法运算符不同的是,等号运算符可以调用被用于可空参数,因为这些运算符会检查参数是否为空,当两个参数都为null时会返回true:

data class Point(var x:Int, var y:Int) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other !is Point) return false
        return x == other.x && y == other.y
    }
}

var point1 = Point(1,2)
var point2 = Point(1,2)
println(point1 == point2) //true
println(null == point1)   //false

在上面的代码中可以看到,我们使用了override标记equals而不是operator,这是因为equals方法早已经在Any类中就定义了实现,所以我们需要使用override而可以省略已经在Any中标记了的operator。此外,我们还使用了 === (恒等于)来比较参数和调用方法的对象是否有相同的引用(或者当调用者是基本类型的时候。是否是相同的值)。注意,=== 和 !== (与===相反)不可重载,因此不存在对他们的约定。


排序运算符

所有的比较都转换为对 compareTo 的调用,这个函数需要返回 Int 值:

data class Point(var x:Int, var y:Int) {
    operator fun compareTo(other: Point):Int {
        return x + y - other.x - other.y
    }
}

var point1 = Point(1,2)
var point2 = Point(4,2)

println(point1 < point2)//false

point1 < point2 表达式等价于 point1.compareTo(point2) < 0,其余的 > 、<= 、>= 等运算符的处理也都与此相同。


集合和区间的约定

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


get 和 set

在 Kotlin 中,使用下标运算符读取元素会被转换为 get 运算符方法的调用 ,井且写入元素将调用 set:

data class Point(var x:Int, var y:Int) {
   operator fun get(index: Int):Int  = when(index) {
       1 -> x
       2 -> y
       else ->
           throw IndexOutOfBoundsException ("Invalid parameter $index")
   }

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

var point = Point(1,2)
println(point[1]) //1
point[1] = point[2]
println(point[1]) //2

在上面的代码中,point[1] 被转换成了 point.get(1),而 point[1] = point[2] 被转换成了 point.set(1,point.get(2))。get 的参数可以是任何类型,而不只是 Int ,还可以定义具有多个参数的 get 方法;set 的最后一个参数用来接收赋值语句中(等号)右边的值,其他参数作为方括号内的下标。


in操作符

in运算符用作区间或集合检查时 。 相应的函数叫作 contains, !in对应的则是!contains。
for 循环中也可以使用 in 运算符,这时它被用来执行迭代:一个诸如 for(x in list) {...}将被转换成 list.iterator ()的调用,然后就像在 Java 中一样,在它上面重复调用 hasNext 和 next 方法。 这么说有点复杂,举个例子:String 的父类CharSequence 重载了 iterator:

public operator fun CharSequence.iterator(): CharIterator = object : CharIterator() {
    private var index = 0
    public override fun nextChar(): Char = get(index++)
    public override fun hasNext(): Boolean = index < length
}

CharIterator是一个声明了next 和hasNext 的 抽象类。 因此iterator事实上是需要用对象表达式来重载的。


解构声明

解构声明在前面的《Kotlin自学之旅(四)函数、扩展函数》已经提到。结构声明用到了约定的原理,要在解构声明中初始化每个变量,将调用名为 componentN 的函数,其中 N 是声明中变量的位置。换句话说,一个解构声明可以表示成下图的样子:
这里写图片描述
解构声明主要使用场景之一,是从一个函数返回多个值,解构声明不仅可以用作函数中的顶层语句,还可以用在其他可以声明变量的地方,例如 in 循环。当然,不可能定义无限数量的 componentN 函数,标准库只 允许使用此语法来访问一个对象的前五个元素。


委托属性

《Kotlin自学之旅(六)对象和委托》中,我们已经知道了怎么使用委托对象,现在我们来看看怎么使用委托属性。委托属性的基本语法如下:

class Example {
    var p: String by Delegate()
}

编译器创建一个隐藏的辅助属性,并使用委托对象的实例进行初始化,初始属性 p 会委托给该实例。原理类似于下面的代码

class Example {
    private val delegate = Delegate()
    var p: String
        set(value: String) = delegate.setValue(...,value)
        get() = delegate.getValue(...)
}

class Delegate {
    operator fun getValue(...): String {...}
    operator fun setValue(..., s: String) {...}
}

by 右边的表达式不一定是新创建的实例,也可以是函数调用、另 一个属性或任何其他表达式,只要这个表达式的值,是能够被编译器用正确的参数类型来调用getValue 和 setValue 的对象。与其他约定一样, getValue 和 setValue 可以是对象自己声明的方法或扩展函数。


总结

这篇文章主要介绍了Kotlin中的约定特性,Kotlin通过约定大大简化了常见方法的使用,同时提高了程序的可读性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值