跳到主要内容

Kotlin 操作符重载

Kotlin 允许为预定义的操作符集合提供自定义实现。通过操作符重载,可以让自定义类型像内置类型一样使用操作符,使代码更加直观和简洁。

基本概念

操作符函数

使用 operator 关键字标记的函数称为操作符函数。编译器会将操作符映射到对应的函数调用:

data class Point(val x: Int, val y: Int) {
// 重载 + 操作符
operator fun plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
}

fun main() {
val p1 = Point(1, 2)
val p2 = Point(3, 4)

// 使用操作符
val sum = p1 + p2
// 编译器转换为:p1.plus(p2)

println(sum) // Point(x=4, y=6)
}

操作符函数规则

  1. 必须使用 operator 关键字修饰
  2. 函数名必须是指定的操作符函数名
  3. 参数数量必须符合操作符要求
  4. 可以定义为成员函数或扩展函数

算术操作符

二元算术操作符

操作符函数名示例
+plusa + ba.plus(b)
-minusa - ba.minus(b)
*timesa * ba.times(b)
/diva / ba.div(b)
%rema % ba.rem(b)
..rangeToa..ba.rangeTo(b)
data class Vector(val x: Double, val y: Double) {
operator fun plus(other: Vector): Vector =
Vector(x + other.x, y + other.y)

operator fun minus(other: Vector): Vector =
Vector(x - other.x, y - other.y)

operator fun times(scalar: Double): Vector =
Vector(x * scalar, y * scalar)

operator fun div(scalar: Double): Vector =
Vector(x / scalar, y / scalar)
}

fun main() {
val v1 = Vector(3.0, 4.0)
val v2 = Vector(1.0, 2.0)

println(v1 + v2) // Vector(x=4.0, y=6.0)
println(v1 - v2) // Vector(x=2.0, y=2.0)
println(v1 * 2.0) // Vector(x=6.0, y=8.0)
println(v1 / 2.0) // Vector(x=1.5, y=2.0)
}

复合赋值操作符

对于 plusminustimesdivrem 等操作符,Kotlin 自动支持复合赋值形式:

data class Counter(var value: Int) {
operator fun plus(increment: Int): Counter {
return Counter(value + increment)
}
}

fun main() {
var counter = Counter(10)

// 普通写法
counter = counter + 5

// 复合赋值写法
counter += 5 // 等同于 counter = counter + 5

println(counter) // Counter(value=20)
}

如果要支持就地修改(类似 MutableList+=),需要实现 plusAssign

class MutableCounter(var value: Int) {
operator fun plusAssign(increment: Int) {
value += increment
}
}

fun main() {
val counter = MutableCounter(10)
counter += 5 // 调用 plusAssign,就地修改
println(counter.value) // 15
}

一元操作符

操作符函数名示例
+unaryPlus+a
-unaryMinus-a
!not!a
++inca++++a
--deca----a
data class Point(val x: Int, val y: Int) {
operator fun unaryMinus(): Point = Point(-x, -y)

operator fun unaryPlus(): Point = Point(+x, +y)

// inc 和 dec 应该返回新的对象
operator fun inc(): Point = Point(x + 1, y + 1)

operator fun dec(): Point = Point(x - 1, y - 1)
}

fun main() {
var p = Point(3, 4)

println(-p) // Point(x=-3, y=-4)
println(+p) // Point(x=3, y=4)

println(p++) // Point(x=3, y=4) 返回原值
println(p) // Point(x=4, y=5) 值已增加

println(++p) // Point(x=5, y=6) 返回新值
}

比较操作符

相等操作符

==!= 操作符转换为 equals() 方法调用:

class Person(val name: String, val age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Person) return false
return name == other.name && age == other.age
}

override fun hashCode(): Int {
var result = name.hashCode()
result = 31 * result + age
return result
}
}

fun main() {
val p1 = Person("张三", 25)
val p2 = Person("张三", 25)
val p3 = Person("李四", 30)

println(p1 == p2) // true
println(p1 != p3) // true
}

注意=== 是恒等操作符,不能重载,用于检查引用是否相同。

比较操作符

通过实现 Comparable 接口的 compareTo 方法,可以使用 <><=>= 操作符:

data class Version(val major: Int, val minor: Int) : Comparable<Version> {
override fun compareTo(other: Version): Int {
val majorCompare = major.compareTo(other.major)
return if (majorCompare != 0) majorCompare else minor.compareTo(other.minor)
}
}

fun main() {
val v1 = Version(1, 0)
val v2 = Version(1, 1)
val v3 = Version(2, 0)

println(v1 < v2) // true
println(v2 < v3) // true
println(v1 >= v1) // true
println(v3 > v1) // true

// 排序
val versions = listOf(v3, v1, v2)
println(versions.sorted()) // [Version(1, 0), Version(1, 1), Version(2, 0)]
}

索引操作符

get 和 set

通过 getset 操作符,可以使用索引访问语法:

class Matrix(private val data: Array<IntArray>) {
val rows: Int get() = data.size
val cols: Int get() = data[0].size

operator fun get(row: Int, col: Int): Int {
return data[row][col]
}

operator fun set(row: Int, col: Int, value: Int) {
data[row][col] = value
}
}

fun main() {
val matrix = Matrix(arrayOf(
intArrayOf(1, 2, 3),
intArrayOf(4, 5, 6)
))

println(matrix[0, 1]) // 2(访问 matrix.get(0, 1))

matrix[1, 2] = 10 // 调用 matrix.set(1, 2, 10)
println(matrix[1, 2]) // 10
}

索引操作符支持多个参数:

class ThreeDArray(private val x: Int, private val y: Int, private val z: Int) {
private val data = IntArray(x * y * z)

operator fun get(i: Int, j: Int, k: Int): Int {
return data[i * y * z + j * z + k]
}

operator fun set(i: Int, j: Int, k: Int, value: Int) {
data[i * y * z + j * z + k] = value
}
}

contains

in 操作符转换为 contains() 方法调用:

class NumberRange(private val start: Int, private val end: Int) {
operator fun contains(value: Int): Boolean {
return value in start..end
}
}

fun main() {
val range = NumberRange(1, 10)

println(5 in range) // true
println(15 in range) // false
println(15 !in range) // true
}

范围操作符

rangeTo

.. 操作符创建范围,通过 rangeTo 实现:

enum class DayOfWeek {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

operator fun DayOfWeek.rangeTo(other: DayOfWeek): ClosedRange<DayOfWeek> {
return object : ClosedRange<DayOfWeek> {
override val start: DayOfWeek = this@rangeTo
override val endInclusive: DayOfWeek = other
}
}

fun main() {
val workday = DayOfWeek.MONDAY..DayOfWeek.FRIDAY

println(DayOfWeek.WEDNESDAY in workday) // true
println(DayOfWeek.SUNDAY in workday) // false
}

rangeUntil

..< 操作符创建半开范围(Kotlin 1.7+):

fun main() {
val range = 1..<10 // 1 到 9(不包含 10)

println(5 in range) // true
println(10 in range) // false
}

调用操作符

invoke

invoke 操作符允许对象像函数一样被调用:

class Greeter(private val greeting: String) {
operator fun invoke(name: String): String {
return "$greeting, $name!"
}

operator fun invoke(name: String, times: Int): String {
return "$greeting, $name! ".repeat(times).trim()
}
}

fun main() {
val greeter = Greeter("Hello")

// 像函数一样调用对象
println(greeter("World")) // Hello, World!
println(greeter("Kotlin", 2)) // Hello, Kotlin! Hello, Kotlin!
}

应用场景

  • 函数式编程中的高阶函数
  • DSL 构建
  • 策略模式
// DSL 示例
class HtmlBuilder {
private val elements = mutableListOf<String>()

operator fun String.invoke(block: HtmlBuilder.() -> Unit) {
elements.add("<$this>")
block()
elements.add("</$this>")
}

fun text(content: String) {
elements.add(content)
}

fun build(): String = elements.joinToString("\n")
}

fun html(block: HtmlBuilder.() -> Unit): String {
val builder = HtmlBuilder()
builder.block()
return builder.build()
}

fun main() {
val result = html {
"html" {
"body" {
text("Hello, World!")
}
}
}
println(result)
}

解构声明

通过定义 componentN() 操作符函数,支持解构声明:

数据类自动支持

数据类自动生成 component1()component2() 等函数:

data class Person(val name: String, val age: Int)

fun main() {
val person = Person("张三", 25)

// 解构声明
val (name, age) = person
println("$name, $age 岁") // 张三, 25 岁

// 在 for 循环中使用
val people = listOf(Person("张三", 25), Person("李四", 30))
for ((n, a) in people) {
println("$n: $a")
}
}

自定义解构

为非数据类手动定义解构函数:

class Rectangle(val width: Int, val height: Int) {
operator fun component1(): Int = width
operator fun component2(): Int = height
}

fun main() {
val rect = Rectangle(10, 20)
val (w, h) = rect

println("宽度: $w, 高度: $h") // 宽度: 10, 高度: 20
}

Map 的解构

Map 的 Entry 支持 component1()component2()

fun main() {
val map = mapOf("a" to 1, "b" to 2, "c" to 3)

// 解构 Map.Entry
for ((key, value) in map) {
println("$key -> $value")
}

// 解构函数返回的 Pair
fun findUser(): Pair<Int, String> = Pair(1, "张三")
val (id, name) = findUser()
println("ID: $id, Name: $name")
}

跳过不需要的变量

使用 _ 跳过不需要的变量:

data class Triple<A, B, C>(val first: A, val second: B, val third: C)

fun main() {
val triple = Triple(1, "hello", 3.14)

// 只需要第一个和第三个
val (first, _, third) = triple
println("$first, $third") // 1, 3.14
}

位运算操作符

Kotlin 的位运算使用中缀函数而非操作符重载:

fun main() {
val a = 0b1100 // 12
val b = 0b1010 // 10

// 位运算(中缀函数)
println(a and b) // 8 (0b1000)
println(a or b) // 14 (0b1110)
println(a xor b) // 6 (0b0110)
println(a.inv()) // 按位取反
println(a shl 2) // 48 (左移)
println(a shr 2) // 3 (右移)
println(a ushr 2) // 无符号右移
}

操作符重载最佳实践

保持语义一致性

操作符的含义应该符合直觉:

// 好的设计:加法表示合并
operator fun plus(other: Collection<T>): Collection<T>

// 不好的设计:加法做了无关的事情
operator fun plus(element: T): Unit // 应该用 add 方法

避免过度使用

不是所有情况都适合使用操作符:

class Calculator {
// 清晰的方法名更好
fun add(a: Int, b: Int): Int = a + b
fun subtract(a: Int, b: Int): Int = a - b

// 操作符重载可能导致困惑
// operator fun plus(pair: Pair<Int, Int>): Int = pair.first + pair.second
}

注意空安全

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

与 Java 的互操作

Java 类可以调用 Kotlin 的操作符函数:

// Java 代码
Point p1 = new Point(1, 2);
Point p2 = new Point(3, 4);
Point sum = p1.plus(p2); // 必须显式调用方法

操作符速查表

一元操作符

操作符函数说明
+aunaryPlus()一元加
-aunaryMinus()一元减
!anot()逻辑非
a++inc()自增(后置)
++ainc()自增(前置)
a--dec()自减(后置)
--adec()自减(前置)

二元操作符

操作符函数说明
a + bplus(b)加法
a - bminus(b)减法
a * btimes(b)乘法
a / bdiv(b)除法
a % brem(b)取余
a..brangeTo(b)范围
a..<brangeUntil(b)半开范围

复合赋值

操作符函数
a += ba = a.plus(b)a.plusAssign(b)
a -= ba = a.minus(b)a.minusAssign(b)
a *= ba = a.times(b)a.timesAssign(b)
a /= ba = a.div(b)a.divAssign(b)
a %= ba = a.rem(b)a.remAssign(b)

比较和索引

操作符函数说明
a == ba?.equals(b) ?: (b === null)相等
a != b!(a?.equals(b) ?: (b === null))不等
a > ba.compareTo(b) > 0大于
a < ba.compareTo(b) < 0小于
a >= ba.compareTo(b) >= 0大于等于
a <= ba.compareTo(b) <= 0小于等于
a[i]a.get(i)索引取值
a[i] = ba.set(i, b)索引赋值
a in bb.contains(a)包含
a !in b!b.contains(a)不包含

其他

操作符函数说明
a()a.invoke()函数调用
a(b)a.invoke(b)带参数调用
a(b, c)a.invoke(b, c)多参数调用

小结

本章介绍了 Kotlin 操作符重载的核心内容:

  1. 基本概念:使用 operator 关键字定义操作符函数
  2. 算术操作符+-*/%
  3. 比较操作符:通过 equalscompareTo 实现
  4. 索引操作符getsetcontains
  5. 范围操作符rangeTorangeUntil
  6. 调用操作符invoke 让对象像函数一样调用
  7. 解构声明componentN 函数支持解构

操作符重载让代码更加直观和简洁,但应保持语义一致性,避免过度使用。

练习

  1. 为复数类实现算术操作符
  2. 为自定义集合类实现索引操作符
  3. 实现 Comparable 接口,使类支持比较操作
  4. 使用 invoke 操作符实现一个简单的 DSL
  5. 为 Pair 类实现解构声明功能