Skip to content

操作符重载与解构

源:Operator overloading | Destructuring

Kotlin 允许我们为预定义的运算符(如 +, *, [], ())提供自定义实现。这一特性是构建 DSL (领域特定语言) 和优雅 API 的关键。

操作符重载 (Operator Overloading)

要重载操作符,必须使用 operator 修饰符标记函数。

算术与比较

所有算术符号都映射到特定的函数名。

kotlin
data class Point(val x: Int, val y: Int) {
    // 重载 '+' 运算符
    operator fun plus(other: Point): Point {
        return Point(x + other.x, y + other.y)
    }
    
    // 重载 'unaryMinus' (-p)
    operator fun unaryMinus() = Point(-x, -y)
}

val p1 = Point(10, 20)
val p2 = Point(5, 5)
val p3 = p1 + p2 // 等价于 p1.plus(p2) -> Point(15, 25)

索引访问 (Indexed Access)

通过重载 getset,可以让对象像数组或 Map 一样通过 [] 访问。

kotlin
class Grid(val rows: Int, val cols: Int) {
    private val matrix = Array(rows * cols) { 0 }

    // obj[i, j]
    operator fun get(row: Int, col: Int): Int {
        return matrix[row * cols + col]
    }

    // obj[i, j] = value
    operator fun set(row: Int, col: Int, value: Int) {
        matrix[row * cols + col] = value
    }
}

val grid = Grid(2, 2)
grid[0, 1] = 99 // 调用 set(0, 1, 99)
val cell = grid[0, 1] // 调用 get(0, 1)

调用操作符 (Invoke)

重载 invoke 允许对象像函数一样被直接调用。这在 Gradle 脚本或拦截器链设计中非常常见。

kotlin
class Greeter(val name: String) {
    operator fun invoke(msg: String) {
        println("$name says: $msg")
    }
}

val greet = Greeter("Jarvis")
greet("System online") // 等价于 greet.invoke(...)

解构声明进阶 (Destructuring)

解构允许我们将一个对象拆解成多个变量。除了数据类 (Data Class) 自带解构外,我们也可以手动实现。

自定义解构

只需定义 componentN 操作符函数。

kotlin
class Location(val lat: Double, val lng: Double) {
    operator fun component1() = lat
    operator fun component2() = lng
}

val loc = Location(30.0, 120.0)
val (lat, lng) = loc // lat=30.0, lng=120.0

循环与 Map 中的解构

这是解构最实用的场景之一。

kotlin
val map = mapOf("key1" to 1, "key2" to 2)

// Map.Entry 扩展了 component1() 和 component2()
for ((key, value) in map) {
    println("$key -> $value")
}

下划线 (Underscore)

如果不需要某个值,可以用 _ 占位,避免编译器警告未使用的变量。

kotlin
val (_, status) = getResult() // 只关心 status,忽略第一个返回值

类型别名 (Type Aliases)

类型别名不会引入新类型,只是为现有类型取个“绰号”。它在简化泛型复杂度和增强代码语义上非常有用。

kotlin
// 1. 缩短冗长的泛型类型
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

// 2. 为函数类型赋予语义
typealias ClickHandler = (View, Int) -> Unit

fun setHandler(handler: ClickHandler) { /*...*/ }

Inline Classes vs Type Aliases

  • Type Alias: 编译时不产生新类型,完全等价于原类型(零开销,但无类型安全隔离)。
  • Value Class (Inline): 包装了一个值,编译时尽可能优化,但在类型系统上是独立的(类型安全)。