kotlin operator运算符重载函数

你想了解 Kotlin 中可以使用 operator 关键字修饰的方法(运算符重载方法),包括核心规则、常用重载方法列表、实际案例和避坑点,我会系统地帮你梳理清楚,让你知道哪些方法能重载、怎么重载,以及重载的注意事项。

一、核心概念:运算符重载与 operator 关键字

operator 关键字的核心作用是:标记函数为 “运算符重载函数”,让你可以用 Kotlin 内置的运算符(如 +-[]() 等)来调用这个函数,本质是 “语法糖”—— 让代码更简洁、更接近自然语言。

核心规则

  1. 函数名固定:每种运算符对应固定的函数名(如 + 对应 plus[] 对应 get),不能自定义;
  2. 必须加 operator:只有用 operator 修饰的函数,才能被运算符调用;
  3. 参数 / 返回值灵活:函数的参数类型、返回值类型可以自定义(只要符合语法);
  4. 支持扩展函数:不仅可以重载类的成员函数,也可以通过扩展函数重载运算符。

二、常用可重载的运算符及对应方法

以下是 Kotlin 中最常用的可重载运算符,按使用场景分类,方便你查阅:

运算符 对应的函数名 函数说明 使用示例
算术运算符
+ plus 加法(一元 / 二元) a + b → a.plus(b)
- minus 减法(一元 / 二元) a - b → a.minus(b)
* times 乘法 a * b → a.times(b)
/ div 除法 a / b → a.div(b)
% rem 取余(替代 mod,Kotlin 1.1+) a % b → a.rem(b)
++ inc 自增(前缀 / 后缀) a++ → a.inc()
-- dec 自减(前缀 / 后缀) a-- → a.dec()
赋值运算符
+= plusAssign 加法赋值 a += b → a.plusAssign(b)
-= minusAssign 减法赋值 a -= b → a.minusAssign(b)
*= timesAssign 乘法赋值 a *= b → a.timesAssign(b)
/= divAssign 除法赋值 a /= b → a.divAssign(b)
%= remAssign 取余赋值 a %= b → a.remAssign(b)
索引 / 调用运算符
[] get 获取索引值 a[b] → a.get(b)
[]= set 设置索引值 a[b] = c → a.set(b, c)
() invoke 函数调用 a(b) → a.invoke(b)
比较运算符
== equals 相等判断(=== 不可重载) a == b → a.equals(b)
> compareTo 比较大小(返回 Int,兼容 Comparable) a > b → a.compareTo(b) > 0
逻辑 / 位运算符
! not 逻辑非 / 位非 !a → a.not()
&&/` ` 不可重载 逻辑与 / 或无法直接重载 -
in contains 判断是否包含 a in b → b.contains(a)
.. rangeTo 创建范围 a..b → a.rangeTo(b)

三、实战案例:运算符重载

案例 1:算术运算符重载(自定义 “坐标类”)

// 自定义坐标类
data class Point(val x: Int, val y: Int) {
    // 重载 + 运算符(二元):Point + Point
    operator fun plus(other: Point): Point {
        return Point(this.x + other.x, this.y + other.y)
    }

    // 重载 + 运算符(扩展:Point + Int)
    operator fun plus(num: Int): Point {
        return Point(this.x + num, this.y + num)
    }

    // 重载 ++ 运算符(自增)
    operator fun inc(): Point {
        return Point(x + 1, y + 1)
    }
}

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

    // 使用 + 运算符(调用 plus(Point))
    val p3 = p1 + p2
    println(p3) // 输出:Point(x=4, y=6)

    // 使用 + 运算符(调用 plus(Int))
    val p4 = p1 + 5
    println(p4) // 输出:Point(x=6, y=7)

    // 使用 ++ 运算符(调用 inc())
    val p5 = p1++ // 后缀自增,等价于 p1.inc()
    println(p5) // 输出:Point(x=1, y=2)(原对象不变)
    println(++p1) // 前缀自增,输出:Point(x=3, y=4)
}

案例 2:索引运算符重载(自定义 “简易列表”)

class MyList<T>(private val items: MutableList<T> = mutableListOf()) {
    // 重载 [] 运算符(获取值,调用 get)
    operator fun get(index: Int): T {
        if (index < 0 || index >= items.size) {
            throw IndexOutOfBoundsException("索引越界:$index")
        }
        return items[index]
    }

    // 重载 []= 运算符(设置值,调用 set)
    operator fun set(index: Int, value: T) {
        if (index < 0 || index >= items.size) {
            throw IndexOutOfBoundsException("索引越界:$index")
        }
        items[index] = value
    }

    // 重载 += 运算符(调用 plusAssign)
    operator fun plusAssign(value: T) {
        items.add(value)
    }
}

fun main() {
    val list = MyList<String>()
    // 使用 += 运算符(调用 plusAssign)
    list += "Kotlin"
    list += "Operator"

    // 使用 [] 运算符(调用 get)
    println(list[0]) // 输出:Kotlin
    println(list[1]) // 输出:Operator

    // 使用 []= 运算符(调用 set)
    list[1] = "重载"
    println(list[1]) // 输出:重载
}

案例 3:invoke 运算符(让对象像函数一样调用)

// 自定义“计算器”类
class Calculator {
    // 重载 () 运算符(无参数)
    operator fun invoke(): String {
        return "计算器初始化完成"
    }

    // 重载 () 运算符(两个 Int 参数)
    operator fun invoke(a: Int, b: Int): Int {
        return a + b
    }

    // 重载 () 运算符(可变参数)
    operator fun invoke(vararg nums: Int): Int {
        return nums.sum()
    }
}

fun main() {
    val calc = Calculator()

    // 调用无参 invoke
    println(calc()) // 输出:计算器初始化完成

    // 调用双参 invoke
    println(calc(5, 3)) // 输出:8

    // 调用可变参数 invoke
    println(calc(1, 2, 3, 4)) // 输出:10
}

案例 4:in 运算符(判断包含关系)

// 自定义“范围类”
class MyRange(val start: Int, val end: Int) {
    // 重载 in 运算符(调用 contains)
    operator fun contains(num: Int): Boolean {
        return num in start..end
    }
}

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

    // 使用 in 运算符(调用 contains)
    println(5 in range) // 输出:true
    println(15 in range) // 输出:false
}

四、进阶技巧:扩展函数重载运算符

除了成员函数,也可以通过扩展函数为已有类重载运算符(比如给 String 重载 +):

// 为 String 扩展 + 运算符(String + Int)
operator fun String.plus(num: Int): String {
    return this + num.toString()
}

fun main() {
    val str = "年龄:" + 25
    println(str) // 输出:年龄:25
}

五、避坑点(重点)

1. 坑点 1:运算符重载必须遵循 “语义一致性”
虽然 Kotlin 允许你任意实现重载函数的逻辑,但必须符合运算符的常规语义,否则会让代码难以理解:

// 错误示例:+ 运算符实现减法逻辑(语义混乱)
operator fun Point.plus(other: Point): Point {
    return Point(this.x - other.x, this.y - other.y)
}
// 调用 p1 + p2 实际是减法,极易误导他人

✅ 正确做法:重载的运算符逻辑必须和运算符的常规含义一致(+ 做加法,- 做减法)。

2. 坑点 2:赋值运算符(+=)与返回值的关系
如果同时重载了 plusplusAssign,Kotlin 会优先调用 plusAssign;如果只重载 plus+= 会被解析为 a = a + b(需要 plus 返回新对象):

data class Num(var value: Int) {
    // 只重载 plus(无 plusAssign)
    operator fun plus(num: Int): Num {
        return Num(this.value + num)
    }
}

fun main() {
    var n = Num(5)
    n += 3 // 等价于 n = n.plus(3)
    println(n) // 输出:Num(value=8)
}

3. 坑点 3:== 运算符与 equals 的关系

  • == 会调用 equals 方法,且会自动处理 nulla == null 不会空指针);
  • === 是 “引用相等”,无法重载;
class Person(val name: String) {
    override fun equals(other: Any?): Boolean {
        return other is Person && other.name == this.name
    }
}

fun main() {
    val p1 = Person("张三")
    val p2 = Person("张三")
    println(p1 == p2) // 输出:true(调用 equals)
    println(p1 === p2) // 输出:false(引用不同)
}

4. 坑点 4:自增 / 自减运算符的前缀 / 后缀
++a(前缀)和 a++(后缀)都会调用 inc() 方法,但返回值不同:

  • 前缀:先执行 inc(),返回新值;
  • 后缀:先返回原值,再执行 inc()
    Kotlin 会自动处理这个逻辑,你只需要实现 inc() 即可。

总结

  1. 核心规则operator 标记函数为运算符重载函数,每种运算符对应固定的函数名,支持成员函数 / 扩展函数;

  2. 常用重载

    • 算术运算符(+/-/*):对应 plus/minus/times
    • 索引运算符([]/[]=):对应 get/set
    • 调用运算符(()):对应 invoke
    • 包含运算符(in):对应 contains
  3. 避坑关键

    • 运算符逻辑必须符合常规语义;
    • 赋值运算符(+=)优先调用 plusAssign
    • == 调用 equals=== 不可重载。

运算符重载是 Kotlin 提升代码简洁性的重要特性,合理使用可以让代码更接近自然语言(比如 5 in rangerange.contains(5) 更易读),但切忌过度使用 —— 只有在能提升可读性的场景下才重载运算符。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • """1.个性化消息: 将用户的姓名存到一个变量中,并向该用户显示一条消息。显示的消息应非常简单,如“Hello ...
    她即我命阅读 5,542评论 0 6
  • 1、expected an indented block 冒号后面是要写上一定的内容的(新手容易遗忘这一点); 缩...
    庵下桃花仙阅读 1,125评论 1 2
  • 一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V...
    墨雅丫阅读 1,688评论 0 0
  • 跟随樊老师和伙伴们一起学习心理知识提升自已,已经有三个月有余了,这一段时间因为天气的原因休课,顺便整理一下之前学习...
    学习思考行动阅读 1,101评论 0 2
  • 一脸愤怒的她躺在了床上,好几次甩开了他抱过来的双手,到最后还坚决的翻了个身,只留给他一个冷漠的背影。 多次尝试抱她...
    海边的蓝兔子阅读 1,058评论 1 4

友情链接更多精彩内容