Kotlin运算符重载及其他约定摘要

重载算数运算符

data class Point(val x: Int, val y: Int) {
    operator fun plus(other: Point): Point { //定义一个名为plus的方法
         return Point(x + other.x, y + other.y)  //坐标分别相加,然后返回一个新的点
  }
}
>>> val p1 = Point(10, 20)
>>> val p2 = Point(30, 40)
>>> println(p1 + p2) //通过使用+号来调用plus方法
Point(x=40, y=60)

Kotlin 限定了你能重载哪些运算符,以及你需要在你的类里面定义的对应名字的函数,你不能定义自己的运算符。

可重载的二元算术运算符

表达式 函数名
a*b times
a/b div
a%b mod
a+b plus
a-b minus

从 Java 调用 Kotlin 运算符非常容易:因为每个重载的运算符都被定义为一个函数,你可以像普通函数那样调用它们。 当从 Kotlin 调用 Java 的时候,对于与 Kotlin 约定匹配的函数都可以使用运算符语法来调用。 由于 Java 没有定义任何用于标记运算符函数的语法,所以使用operator修饰符的要求对它不适用,唯一的约束是,参数需要匹配名称和数量。

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

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

Kotlin 没有为标准数字类型定义任何按位运算符,它使用中缀调用语法的常规函数。

以下是 Kotlin 提供的,用于执行位运算的完整函数列表:

  • shl — 带符号左移
  • shr — 带符号右移
  • ushr — 无符号右移
  • and — 按位与
  • or — 按位或
  • xor — 按位异或
  • inv — 按位取反

下面的示例展示了这些函数的使用方法:

>>> println(0x0F and 0xF0)
0
>>> println(0x0F or 0xF0)
255
>>> println(0x1 shl 4)
16

集合运算符

Kotlin 标准库为可变集合定义了plusAssign函数:

operator fun <T> MutableCollection<T>.plusAssign(element: T) {
    this.add(element)
}

用法

>>> val numbers = ArrayList<Int>()
>>> numbers += 42
>>> println(numbers[0])
42

当你在代码里面用到+=的时候,理论上plusplusAssign都会被调用到。如果在这种情况下,两个函数都有定义且适用,编译器会报一个错误。

注意事项:

Kotlin 标准库支持集合的两种方法。+-运算符总是返回一个新的集合。+=-=运算符可以用于可变集合,始终在一个地方修改他们;而它们用于只读集合时,会返回一个修改过的副本。(这意味着只有当引用只读集合的变量被声明为var的时候,才能使用+=-=。)

重载一元运算符

可重载的一元的算法运算符

表达式 函数名
+a unaryPlus
-a unaryMinus
!a not
++a, a++ inc
--a, a-- dec

重载比较运算符

在 Kotlin 里面,你可以对任何对象使用比较运算符(==!=><等等),而不仅仅限于基本数据类型。 不像Java一样需要调用equalscompareTo函数,你可以直接使用比较运算符。

注意,===(恒等)运算符不能被重载,恒等运算符与Java中的==运算符是完全相同的。

“get”和“set”
data class MutablePoint(var x: Int, var y: Int)
operator fun MutablePoint.set(index: Int, value: Int) {
//定义一个运算符函数名为set
    when(index) {
        0 -> x = value //根据给出的index修改对应的坐标
        1 -> y = value
        else ->
             throw IndexOutOfBoundsException("Invalid coordinate $index")
   }
}

>>> val p = MutablePoint(10, 20)
>>> p[1] = 42
>>> println(p)
MutablePoint(x=10, y=42)
operator fun Point.get(index: Int): Int {
    return when(index) { //定义一个运算符函数名为get
        0 -> x //根据给出的index返回对应的坐标
        1 -> y
        else ->
              throw IndexOutOfBoundsException("Invalid coordinate $index")
      }
}

>>> val p = Point(10, 20)
>>> println(p[1])
20
“in”的约定

相应的函数叫做contains

operator fun Rectangle.contains(p: Point): Boolean {
    return p.x in upperLeft.x until lowerRight.x &&
           p.y in upperLeft.y until lowerRight.y
} //构建一个区间,检查坐标x是否属于这个区间
//使用until函数来构建一个开区间
>>> val rect = Rectangle(Point(10, 20), Point(50, 50))
>>> println(Point(20, 30) in rect)
true
>>> println(Point(5, 5) in rect)
false
rangTo的约定

..运算符是调用rangeTo函数的一个简洁方法。

operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>

解构声明

一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量。

>>> val p = Point(10, 20)
>>> val (x, y) = p //声明变量x,y,然后用p的组件来初始化
>>> println(x)
10
>>> println(y)
20

要在解构声明中初始化每个变量,将调用名为componentN的函数,其中N是声明中变量的位置。

  • 对于数据类,编译器为每个在主构造方法中声明的属性生成一个componentN函数。

  • 手动为非数据类声明这些功能:

    class Point(val x: Int, val y: Int) {
        operator fun component1() = x
        operator fun component2() = y
    }
    
  • 标准库只允许使用此语法来访问一个对象的前五个元素。

解构声明不仅可以用作函数中的顶层语句,还可以用在其他可以声明变量的地方,例如in循环。

fun printEntries(map: Map<String, String>) {
    for ((key, value) in map) { //在in循环中用解构声明
        println("$key -> $value")
    }
}

Kotlin 标准库给map增加了一个扩展的iterator函数,用来返回map条目的迭代器。因此,与Java不同的是,你可以直接迭代map。

for (entry in map.entries) {
    val key = entry.component1()
    val value = entry.component2()
    // ...
}

委托属性

基本语法

class Foo {
    var p: Type by Delegate()
}

惰性初始化和"by lazy()"

惰性初始化 是一种常见的模式,直到在第一次访问时该属性的时候,才根据需要创建一部分对象

class Person(val name: String) {
    val emails by lazy { loadEmails(this) }
}

默认情况下,lazy函数是线程安全的,如果需要,你可以设置其他选项来告诉它要使用哪个锁,或者完全避开同步,如果该类永远不会在多线程环境中使用。

注意,要实现自己的可以用作委托的类,或者说实现自定义的委托属性,请参考《Kotlin 实战》一书 7.5.3节。理解代理属性的原理,并在实际编程中使用,会增加代码的简介度。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容