Kotlin基础知识点 #107: 操作符重载(operator overloading)

难度: ⭐⭐⭐

问题背景

在数学和日常编程中,我们习惯使用+-*等操作符。但当我们定义自己的类时,如何让这些操作符也能用于我们的类呢?例如,如何实现两个复数相加、两个向量相乘?

Kotlin的操作符重载功能允许我们为自定义类型定义这些操作符的行为,使代码更加直观和简洁。

核心概念

操作符重载允许你为预定义的操作符(如+-*[]等)定义自定义行为。通过使用operator关键字修饰函数,并使用特定的函数名,就可以实现操作符重载。

语法格式:

operator fun 函数名(参数): 返回类型 {
    // 实现
}

重要规则

  1. 必须使用operator关键字
  2. 必须使用Kotlin规定的特定函数名
  3. 可以是成员函数或扩展函数
  4. 参数个数必须符合操作符的要求

代码示例

示例1:算术操作符重载

// 定义一个复数类
data class Complex(val real: Double, val imaginary: Double) {
    // 重载 + 操作符
    operator fun plus(other: Complex): Complex {
        return Complex(real + other.real, imaginary + other.imaginary)
    }

    // 重载 - 操作符
    operator fun minus(other: Complex): Complex {
        return Complex(real - other.real, imaginary - other.imaginary)
    }

    // 重载 * 操作符
    operator fun times(other: Complex): Complex {
        return Complex(
            real * other.real - imaginary * other.imaginary,
            real * other.imaginary + imaginary * other.real
        )
    }

    // 重载一元 - 操作符
    operator fun unaryMinus(): Complex {
        return Complex(-real, -imaginary)
    }

    override fun toString() = "$real + ${imaginary}i"
}

// 使用
fun main() {
    val c1 = Complex(3.0, 4.0)
    val c2 = Complex(1.0, 2.0)

    println(c1 + c2)  // 4.0 + 6.0i
    println(c1 - c2)  // 2.0 + 2.0i
    println(c1 * c2)  // -5.0 + 10.0i
    println(-c1)      // -3.0 + -4.0i
}

示例2:比较操作符重载

// 定义一个版本号类
data class Version(val major: Int, val minor: Int, val patch: Int) : Comparable<Version> {
    // 重载 > 操作符
    operator fun compareTo(other: Version): Int {
        if (major != other.major) return major - other.major
        if (minor != other.minor) return minor - other.minor
        return patch - other.patch
    }

    override fun toString() = "$major.$minor.$patch"
}

// 使用
fun main() {
    val v1 = Version(1, 2, 3)
    val v2 = Version(1, 3, 0)
    val v3 = Version(2, 0, 0)

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

示例3:索引访问操作符重载

// 定义一个矩阵类
class Matrix(private val rows: Int, private val cols: Int) {
    private val data = Array(rows) { DoubleArray(cols) }

    // 重载 [] 操作符(get)
    operator fun get(row: Int, col: Int): Double {
        return data[row][col]
    }

    // 重载 [] 操作符(set)
    operator fun set(row: Int, col: Int, value: Double) {
        data[row][col] = value
    }

    override fun toString(): String {
        return data.joinToString("\n") { row ->
            row.joinToString(" ") { "%.2f".format(it) }
        }
    }
}

// 使用
fun main() {
    val matrix = Matrix(2, 2)

    // 使用 [] 操作符赋值
    matrix[0, 0] = 1.0
    matrix[0, 1] = 2.0
    matrix[1, 0] = 3.0
    matrix[1, 1] = 4.0

    // 使用 [] 操作符读取
    println(matrix[0, 0])  // 1.0

    println(matrix)
    // 1.00 2.00
    // 3.00 4.00
}

示例4:in操作符重载

// 定义一个日期范围类
data class DateRange(val start: Date, val end: Date) {
    // 重载 in 操作符
    operator fun contains(date: Date): Boolean {
        return date >= start && date <= end
    }
}

// 定义一个矩形类
data class Rectangle(val x: Int, val y: Int, val width: Int, val height: Int) {
    // 重载 in 操作符,判断点是否在矩形内
    operator fun contains(point: Point): Boolean {
        return point.x in x..(x + width) && point.y in y..(y + height)
    }
}

data class Point(val x: Int, val y: Int)

// 使用
fun main() {
    val rect = Rectangle(0, 0, 100, 100)
    val point1 = Point(50, 50)
    val point2 = Point(150, 150)

    println(point1 in rect)  // true
    println(point2 in rect)  // false
}

示例5:invoke操作符重载

// 定义一个函数包装类
class Greeter(val greeting: String) {
    // 重载 () 操作符
    operator fun invoke(name: String) {
        println("$greeting, $name!")
    }
}

// 定义一个计算器类
class Calculator {
    operator fun invoke(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
        return operation(a, b)
    }
}

// 使用
fun main() {
    val greeter = Greeter("Hello")
    greeter("John")  // Hello, John!
    greeter("Jane")  // Hello, Jane!

    val calc = Calculator()
    println(calc(10, 5, Int::plus))   // 15
    println(calc(10, 5, Int::times))  // 50
}

示例6:增强赋值操作符

// 定义一个可变列表包装类
class MyList<T> {
    private val items = mutableListOf<T>()

    // 重载 += 操作符
    operator fun plusAssign(item: T) {
        items.add(item)
    }

    // 重载 -= 操作符
    operator fun minusAssign(item: T) {
        items.remove(item)
    }

    override fun toString() = items.toString()
}

// 使用
fun main() {
    val list = MyList<Int>()

    list += 1
    list += 2
    list += 3
    println(list)  // [1, 2, 3]

    list -= 2
    println(list)  // [1, 3]
}

Kotlin操作符对应的函数名

算术操作符

操作符 函数名 说明
a + b plus 加法
a - b minus 减法
a * b times 乘法
a / b div 除法
a % b rem 取余
a…b rangeTo 范围

一元操作符

操作符 函数名 说明
+a unaryPlus 一元加
-a unaryMinus 一元减
!a not 逻辑非
++a, a++ inc 自增
–a, a– dec 自减

比较操作符

操作符 函数名 说明
a > b compareTo 大于
a < b compareTo 小于
a >= b compareTo 大于等于
a <= b compareTo 小于等于

索引访问操作符

操作符 函数名 说明
a[i] get 读取索引
a[i] = b set 设置索引
a in b contains 包含

调用操作符

操作符 函数名 说明
a() invoke 函数调用

增强赋值操作符

操作符 函数名 说明
a += b plusAssign 加并赋值
a -= b minusAssign 减并赋值
a *= b timesAssign 乘并赋值
a /= b divAssign 除并赋值
a %= b remAssign 取余并赋值

关键要点

  1. 优先级和结合性:重载的操作符保持原有的优先级和结合性

  2. equals和hashCode==操作符会自动调用equals()方法,不需要重载

  3. plus vs plusAssign

    • plus返回新对象:val c = a + b
    • plusAssign修改原对象:a += b
  4. 可空性:操作符重载也支持可空类型

  5. 扩展函数:可以通过扩展函数为已有类添加操作符重载

    operator fun String.times(n: Int): String = this.repeat(n)
    println("Hello" * 3)  // HelloHelloHello
    

相关知识点

  • 中缀函数(#106):另一种自定义操作符的方式
  • 扩展函数(#104):可以用扩展函数实现操作符重载
  • data class(#108):自动生成一些操作符函数
  • 委托属性getValuesetValue也是操作符

最佳实践

  1. 只在操作符的语义明确且符合直觉时使用重载
  2. 保持操作符的数学和逻辑意义
  3. 不要过度使用,避免让代码难以理解
  4. 为自定义类型实现完整的操作符集合
  5. 考虑实现equals()hashCode()toString()方法
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐