运算符重载¶
Kotlin 允许为一组固定运算符提供自定义实现,例如 +、-、[]、in、比较运算等。运算符重载能让领域模型更自然,但也很容易写出“看起来简洁、实际难懂”的 API。
基本规则¶
要重载运算符,需要定义特定名称的函数,并加上 operator 修饰符:
data class Point(val x: Int, val y: Int)
operator fun Point.plus(other: Point): Point =
Point(x + other.x, y + other.y)
val a = Point(1, 2)
val b = Point(3, 4)
println(a + b) // Point(x=4, y=6)
a + b 会被翻译成 a.plus(b)。
一元运算符¶
operator fun Point.unaryMinus(): Point =
Point(-x, -y)
println(-Point(1, 2)) // Point(x=-1, y=-2)
常见映射:
| 表达式 | 函数 |
|---|---|
+a |
a.unaryPlus() |
-a |
a.unaryMinus() |
!a |
a.not() |
自增与自减¶
data class Counter(val value: Int) {
operator fun inc(): Counter = Counter(value + 1)
}
var counter = Counter(0)
counter++
inc() 和 dec() 应返回同一类型或其子类型,不能直接修改不可变值对象自身。
二元运算符¶
| 表达式 | 函数 |
|---|---|
a + b |
a.plus(b) |
a - b |
a.minus(b) |
a * b |
a.times(b) |
a / b |
a.div(b) |
a % b |
a.rem(b) |
示例:
data class Money(val cents: Long) {
operator fun plus(other: Money): Money =
Money(cents + other.cents)
}
这种场景下 + 的含义符合直觉,适合重载。
[] 访问¶
class Matrix(private val values: List<List<Int>>) {
operator fun get(row: Int, col: Int): Int =
values[row][col]
}
val matrix = Matrix(listOf(listOf(1, 2), listOf(3, 4)))
println(matrix[1, 0]) // 3
可写访问:
class MutableMatrix(private val values: Array<IntArray>) {
operator fun get(row: Int, col: Int): Int = values[row][col]
operator fun set(row: Int, col: Int, value: Int) {
values[row][col] = value
}
}
in 与 !in¶
data class RangeBox(val start: Int, val end: Int) {
operator fun contains(value: Int): Boolean =
value in start..end
}
val box = RangeBox(1, 10)
println(5 in box)
a in b 会调用 b.contains(a)。
调用对象:invoke¶
class Multiplier(private val factor: Int) {
operator fun invoke(value: Int): Int = value * factor
}
val double = Multiplier(2)
println(double(21)) // 42
invoke 适合函数对象、策略对象、轻量 DSL。不要为了“炫技”让普通对象也像函数一样调用。
比较运算¶
<、>、<=、>= 基于 compareTo:
data class Version(val major: Int, val minor: Int) : Comparable<Version> {
override fun compareTo(other: Version): Int =
compareValuesBy(this, other, Version::major, Version::minor)
}
println(Version(1, 2) < Version(2, 0))
相等运算 == 不通过运算符重载函数实现,而是调用 equals()。不要试图用 operator 改写 == 的语义。
解构声明¶
数据类自动生成 componentN():
data class User(val name: String, val age: Int)
val (name, age) = User("Ada", 36)
普通类也可以手写:
class PairLike(val first: String, val second: String) {
operator fun component1() = first
operator fun component2() = second
}
Java 对比¶
Java 不支持通用运算符重载。你通常会写:
moneyA.plus(moneyB)
Kotlin 可以写:
moneyA + moneyB
优势是领域表达更自然;风险是运算符含义被滥用后,代码读者需要猜测 + 到底做了什么。
实践建议¶
- 只在运算符含义符合直觉时重载。
- 金额、向量、矩阵、范围、索引容器是比较自然的场景。
- 不要让
+做数据库写入、网络请求、日志上报等有强副作用的事情。 invoke、解构和 DSL 能提升表达力,但要控制使用范围。- 公共 API 中重载运算符时,应同时提供命名函数或清晰文档。
参考¶
- 官方运算符重载:https://kotlinlang.org/docs/operator-overloading.html