你想了解 Kotlin 中可以使用 operator 关键字修饰的方法(运算符重载方法),包括核心规则、常用重载方法列表、实际案例和避坑点,我会系统地帮你梳理清楚,让你知道哪些方法能重载、怎么重载,以及重载的注意事项。
一、核心概念:运算符重载与 operator 关键字
operator 关键字的核心作用是:标记函数为 “运算符重载函数”,让你可以用 Kotlin 内置的运算符(如 +、-、[]、() 等)来调用这个函数,本质是 “语法糖”—— 让代码更简洁、更接近自然语言。
核心规则
-
函数名固定:每种运算符对应固定的函数名(如
+对应plus,[]对应get),不能自定义; -
必须加 operator:只有用
operator修饰的函数,才能被运算符调用; - 参数 / 返回值灵活:函数的参数类型、返回值类型可以自定义(只要符合语法);
- 支持扩展函数:不仅可以重载类的成员函数,也可以通过扩展函数重载运算符。
二、常用可重载的运算符及对应方法
以下是 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:赋值运算符(+=)与返回值的关系
如果同时重载了 plus 和 plusAssign,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方法,且会自动处理null(a == 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()即可。
总结
核心规则:
operator标记函数为运算符重载函数,每种运算符对应固定的函数名,支持成员函数 / 扩展函数;-
常用重载:
- 算术运算符(
+/-/*):对应plus/minus/times; - 索引运算符(
[]/[]=):对应get/set; - 调用运算符(
()):对应invoke; - 包含运算符(
in):对应contains;
- 算术运算符(
-
避坑关键:
- 运算符逻辑必须符合常规语义;
- 赋值运算符(
+=)优先调用plusAssign; -
==调用equals,===不可重载。
运算符重载是 Kotlin 提升代码简洁性的重要特性,合理使用可以让代码更接近自然语言(比如 5 in range 比 range.contains(5) 更易读),但切忌过度使用 —— 只有在能提升可读性的场景下才重载运算符。