运算符重载

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