Kotlin 笔记:运算符重载及其他约定

10 篇文章 0 订阅

在 Kotlin 中,如果一个类定义了一个名为 plus 的函数,那就可以在该类的实例上使用 + 运算符。这种技术称为约定

下面看看 Kolint 中约定的使用。

重载算术运算符

重载二元算术运算符

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

val a = Point(1, 2)     // Point(x=1, y=2)
val b = a + Point(3, 4) // Point(x=3, y=6)
// 相当于 val b = a.plus(Point(3, 4))

使用扩展函数也可以:

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

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

表达式函数名
a * btimes
a / bdiv
a % bmod
a + bplus
a - bminus
位运算

Kotlin 中没有位运算符,因此也无法自定义。Kotlin 中的位运算使用常规函数进行:

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

重载复合赋值运算符

有时我们会用到 +=、-=、*= 这种复合赋值运算符。

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

var a = Point(1, 2)
a += Point(3, 4)    // Point(x=4, y=6),一个新的 Point 对象
// 相当于 a = a + Point(3, 4)

当定义了 + 运算符后,就可以使用 +=,这时 a 会指向一个新的对象。所以 a 必须是 var 的。

有时我们操作 += 时,其实是改变 a 的内容,并不想换一个新的对象,那可以重载 plusAssign 方法(类似的有 minusAssign 等方法)。

operator fun Point.plusAssign(other: Point) {
    this.x += other.x
    this.y += other.y
}

val a = Point(1, 2)
a += Point(3, 4)    // // Point(x=4, y=6),还是原来的 Point 对象

这时 a 的引用并没有变,所以它可以是 val 的。

可以看出,+= 运算符可能代表两种函数,一种是 plus,一种是 plusAssign,当两个函数都有定义时,可以通过 var、val 进行区分。但最好还是不要定义两个。

重载一元运算符

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

val a = Point(1, 2) // Point(x=1, y=2)
val b = -a          // Point(x=-1, y=-2)

可重载的一元运算符:

表达式函数名
+aunaryPlus
-aunaryMinus
!anot
++a, a++inc
–a, a–dec

重载比较表达式

equals

== 运算符,会被转换为 equals 的调用。

a == b
// 相当于
a?.equals(b) ?: (b == null)

重载 equals 方法:

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

上面用到了 === 运算符,这和 Java 的 == 一致,表示两个参数是不是同一个对象。=== 运算符不能被重载。

equals 使用 override 关键字进行修饰,而不是其他运算符的 operator 关键字。因为 equals 在 Any 类中定义时已经带了 operator 关键字。

compareTo

>、>=、<、<= 运算符会被转换为 compareTo 的调用。

compareTo 是 Java 中 Comparable 接口对象比较的简明语法。

// java
a.compareTo(b) >= 0
// kotlin
a >= b

Kotlin 标准库中的 compareValuesBy 函数也提供了 compareTo 的简洁实现。

val p1 = Point(1, 2)
val p2 = Point(3, 4)
println(compareValuesBy(p1, p2) {it.x})

集合和区间的约定

下标访问:get 和 set

[ ] 操作符会被转换为 get、set 的调用。

实现 get 方法:

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


val a = Point(1, 2)
a[0]        // 1

实现 set 方法:

operator fun Point.set(index: Int, value: Int) {
    when (index) {
        0 -> this.x = value
        1 -> this.y = value
        else -> throw Exception("invalid index")
    }
}

val a = Point(1, 2)
a[1] = 8
a   // Point(x=1, y=2)

in 的约定

in 操作符会被转换为 contains 的调用。

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
}

val rect = Rectangle(Point(10, 20), Point(50, 50))
println(Point(20, 30) in rect)  // true
println(Point(1, 2) in rect)    // false

rangeTo 的约定

… 运算符会被转换为 rangeTo 的调用。

val now = LocalDate.now()
val vacation = now..now.plusDays(7)
println(now.plusDays(2) in vacation)    // true

now..now.plusDays(7) 会被转换为 now.rangeTo()。rangeTo 并不是 LocalDate 的成员函数,而是 Comparable 的一个扩展函数。所以实现了 Comparable 接口的函数都可以直接使用 … 运算符。

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

在“for”循环中使用“iterator”的约定

在 for 循环中使用 in,会调用到 iterator 方法。如 for(x in list) {…} 会被转换为 list.iterator() 的调用。

operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
            object : Iterator<LocalDate> {
                var current = start

                override fun hasNext(): Boolean = current <= endInclusive

                override fun next(): LocalDate = current.apply {
                    current = plusDays(1)
                }
            }
            
val now = LocalDate.now()
val vacation = now..now.plusDays(2)
for (day in vacation) {
    println(day)
}

// 输出
2018-10-29
2018-10-30
2018-10-31

解构声明和组件函数

Kotlin 支持像 python 一样,把参数分离出来:

val (name, age) = person

这种操作被称为解构声明,解构声明会被转换为 componentN 函数的调用。

使用前,用 operator 关键字来标识 componentN 方法(最多可以有5个):

class Pair<K, V, W>(val first: K, val second: V, val third: W) {
    operator fun component1(): K = first

    operator fun component2(): V = second

    operator fun component3(): W = third
}

数据类默认声明了 componentN() 方法,可以直接使用。

解构声明和 map:

for ((key, value) in map) {
}

下划线标识用不到的变量:

val (_, status) = getResult()

lambda 解构:

map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }

两个参数和一个参数解构的区别:

{ a -> ... } // one parameter
{ a, b -> ... } // two parameters
{ (a, b) -> ... } // a destructured pair
{ (a, b), c -> ... } // a destructured pair and another parameter

可以声明整个解构参数的类型,也可以单独声明某个 component 的类型:

map.mapValues { (_, value): Map.Entry<Int, String> -> "$value!" }
map.mapValues { (_, value: String) -> "$value!" }

重用属性访问的逻辑:委托属性

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

通过 by 关键字,引入委托属性。p 属性的 get()、set() 方法会被委托给 Delegate 对象的 getValue()、setValue() 方法。

class Delegate {
    operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${prop.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) {
        println("$value has been assigned to ${prop.name} in $thisRef")
    }
}

执行get、set。

val example = Example()
println(example.p)

example.p = "hello"

// output
Example@179d3b25, thank you for delegating 'p' to me!
hello has been assigned to p in Example@179d3b25

thisRef — 被委托的类或它的超类
prop — KProperty<*> 或它的超类

库函数的委托方法

lazy
val q: String by lazy {
    println("hello")
    "world"
}
println(q)
println(q)
println(q)

// output
hello
world
world
world

lazy 提供一个拥有 getValue 方法的委托对象,只在被委托的对象第一次调用时初始化一次,之后都返回第一次初始化后的值。lazy 函数是线程安全的。

observable
class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$prop, $old -> $new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "first"
    user.name = "second"
}

// output
var User.name: kotlin.String,<no name> -> first
var User.name: kotlin.String,first -> second

observable 可以添加属性改变的观察者。它接收两个参数,一个初始值,一个值变化的处理器,每次属性值变化后都会调用这个处理器。处理器提供三个参数,被改变的属性、属性的旧值、属性的新值。

把属性存入 map

Map、MutableMap 接口定义了 getValue 、setValue 函数,可以把属性委托给它们。

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

val user = User(mapOf(
        "name" to "John Doe",
        "age" to 25
))

println(user.name)    // "John Doe"
println(user.age)    // 25
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值