Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

使用操作符

在实际应用中,你很少会重载操作符 - 通常只有在创建自己的库时才会这样做。

然而,你经常会使用重载的操作符,通常是在不经意间使用。例如,Kotlin 标准库定义了许多操作符,可以改进集合的使用体验。以下是从新角度看待的一些熟悉代码:

// UsingOperators/NewAngle.kt
import atomictest.eq

fun main() {
  val list = MutableList(10) { 'a' + it }
  list[7] eq 'h' // operator get()
  list.get(8) eq 'i' // 显式调用
  list[9] = 'x' // operator set()
  list.set(9, 'x') // 显式调用
  list[9] eq 'x'
  ('d' in list) eq true // operator contains()
  list.contains('e') eq true // 显式调用
}

使用方括号访问列表元素会调用重载的 get()set() 操作符,而 in 运算符则调用了 contains()

在可变集合上调用 += 会修改它,而调用 + 则返回一个新的集合,其中包含旧元素和新元素:

// UsingOperators/OperatorPlus.kt
import atomictest.eq

fun main() {
  val mutableList = mutableListOf(1, 2, 3)
  mutableList += 4  // operator plusAssign()
  mutableList.plusAssign(5) // 显式调用
  mutableList eq "[1, 2, 3, 4, 5]"
  mutableList + 99 eq "[1, 2, 3, 4, 5, 99]"
  mutableList eq "[1, 2, 3, 4, 5]"
  val list = listOf(1)  // 只读
  val newList = list + 2  // operator plus()
  list eq "[1]"
  newList eq "[1, 2]"
  val another = list.plus(3)  // 显式调用
  another eq "[1, 3]"
}

在只读集合上调用 += 可能不会产生你期望的结果:

// UsingOperators/Unexpected.kt
import atomictest.eq

fun main() {
  var list = listOf(1, 2)
  list += 3  // 可能是意外的
  list eq "[1, 2, 3]"
}

在可变集合中,a += b 调用 plusAssign() 来修改 a。然而,对于只读集合,plusAssign() 是不可用的,因此 Kotlin 会将 a += b 重写为 a = a + b。这会调用 plus(),它不会改变集合,而是创建一个新的集合并将结果分配给 var list 引用。总体效果是 a += b 仍然为我们所期望的 a 的结果 - 至少对于像 Int 这样的简单类型来说。

// UsingOperators/ReadOnlyAndPlus.kt
import atomictest.eq

fun main() {
  var list = listOf(1, 2)
  val initial = list
  list += 3
  list eq "[1, 2, 3]"
  list = list.plus(4)
  list eq "[1, 2, 3, 4]"
  initial eq "[1, 2]"
}

最后一行显示了 initial 集合保持不变。为每个添加的元素创建一个新集合可能不是你的意图。如果你在 list 上使用 val 而不是 var,则使用 += 将无法编译。这是使用 val 的另一个原因 - 只有在必要时使用 var

compareTo()操作符重载 中作为独立的扩展函数被引入。然而,如果你的类实现了 Comparable 接口并且重写了其 compareTo(),则会获得更大的好处:

// UsingOperators/CompareTo.kt
package usingoperators
import atomictest.eq

data class Contact(
  val name: String,
  val mobile: String
): Comparable<Contact> {
  override fun compareTo(
    other: Contact
  ): Int = name.compareTo(other.name)
}

fun main() {
  val alice = Contact("Alice", "0123456789")
  val bob = Contact("Bob", "9876543210")
  val carl = Contact("Carl", "5678901234")
  (alice < bob) eq true
  (alice <= bob) eq true
  (alice > bob) eq false
  (alice >= bob) eq false
  val contacts = listOf(bob, carl, alice)
  contacts.sorted() eq
    listOf(alice, bob, carl)
  contacts.sortedDescending() eq
    listOf(carl, bob, alice)
}

任何两个 Comparable 都可以使用 <<=>>= 进行比较(请注意,==!= 不包括在内)。Kotlin 不需要在重写 compareTo() 时使用 operator 修饰符,因为它在 Comparable 接口中已经被定义为 operator

实现 Comparable 也使得类具有可排序性,可以创建一个实例的范围而无需重新定义 .. 操作符。然后,你可以检查一个值是否在该范围内:

// UsingOperators/ComparableRange.kt
package usingoperators
import atomictest.eq

class F(val i: Int): Comparable<F> {
  override fun compareTo(other: F) =
    i.compareTo(other.i)
}

fun main() {
  val range = F(1)..F(7)
  (F(3) in range) eq true
  (F(9) in range) eq false
}

最好是实现 Comparable。只有在使用你无法控制的类时,才将 compareTo() 定义为扩展函数。

解构操作符

另一组通常不会定义

的操作符是 componentN() 函数(component1()component2() 等),用于 解构声明。在 main() 中,Kotlin 静默地生成了对解构赋值的 component1()component2() 的调用:

// UsingOperators/DestructuringDuo.kt
package usingoperators
import atomictest.*

class Duo(val x: Int, val y: Int) {
  operator fun component1(): Int {
    trace("component1()")
    return x
  }
  operator fun component2(): Int {
    trace("component2()")
    return y
  }
}

fun main() {
  val (a, b) = Duo(1, 2)
  a eq 1
  b eq 2
  trace eq "component1() component2()"
}

相同的方法也适用于 Map,它们使用包含 component1()component2() 成员函数的 Entry 类型:

// UsingOperators/DestructuringMap.kt
import atomictest.eq

fun main() {
  val map = mapOf("a" to 1)
  for ((key, value) in map) {
    key eq "a"
    value eq 1
  }
  // 解构赋值变成了:
  for (entry in map) {
    val key = entry.component1()
    val value = entry.component2()
    key eq "a"
    value eq 1
  }
}

你可以在任何 data 类上使用解构声明,因为 componentN() 函数会被自动生成:

// UsingOperators/DestructuringData.kt
package usingoperators
import atomictest.eq

data class Person(
  val name: String,
  val age: Int
) {
  // 编译器生成:
  // fun component1() = name
  // fun component2() = age
}

fun main() {
  val person = Person("Alice", 29)
  val (name, age) = person
  // 解构赋值变成了:
  val name_ = person.component1()
  val age_ = person.component2()
  name eq "Alice"
  age eq 29
  name_ eq "Alice"
  age_ eq 29
}

Kotlin 会为每个属性生成一个 componentN() 函数。

练习和解答可在 www.AtomicKotlin.com 找到。