使用操作符
在实际应用中,你很少会重载操作符 - 通常只有在创建自己的库时才会这样做。
然而,你经常会使用重载的操作符,通常是在不经意间使用。例如,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 找到。