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)
}
操作符函数规则
- 必须使用
operator关键字修饰 - 函数名必须是指定的操作符函数名
- 参数数量必须符合操作符要求
- 可以定义为成员函数或扩展函数
算术操作符
二元算术操作符
| 操作符 | 函数名 | 示例 |
|---|---|---|
+ | 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 | a % b → a.rem(b) |
.. | rangeTo | a..b → a.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)
}
复合赋值操作符
对于 plus、minus、times、div、rem 等操作符,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 |
++ | inc | a++、++a |
-- | dec | a--、--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
通过 get 和 set 操作符,可以使用索引访问语法:
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); // 必须显式调用方法
操作符速查表
一元操作符
| 操作符 | 函数 | 说明 |
|---|---|---|
+a | unaryPlus() | 一元加 |
-a | unaryMinus() | 一元减 |
!a | not() | 逻辑非 |
a++ | inc() | 自增(后置) |
++a | inc() | 自增(前置) |
a-- | dec() | 自减(后置) |
--a | dec() | 自减(前置) |
二元操作符
| 操作符 | 函数 | 说明 |
|---|---|---|
a + b | plus(b) | 加法 |
a - b | minus(b) | 减法 |
a * b | times(b) | 乘法 |
a / b | div(b) | 除法 |
a % b | rem(b) | 取余 |
a..b | rangeTo(b) | 范围 |
a..<b | rangeUntil(b) | 半开范围 |
复合赋值
| 操作符 | 函数 |
|---|---|
a += b | a = a.plus(b) 或 a.plusAssign(b) |
a -= b | a = a.minus(b) 或 a.minusAssign(b) |
a *= b | a = a.times(b) 或 a.timesAssign(b) |
a /= b | a = a.div(b) 或 a.divAssign(b) |
a %= b | a = a.rem(b) 或 a.remAssign(b) |
比较和索引
| 操作符 | 函数 | 说明 |
|---|---|---|
a == b | a?.equals(b) ?: (b === null) | 相等 |
a != b | !(a?.equals(b) ?: (b === null)) | 不等 |
a > b | a.compareTo(b) > 0 | 大于 |
a < b | a.compareTo(b) < 0 | 小于 |
a >= b | a.compareTo(b) >= 0 | 大于等于 |
a <= b | a.compareTo(b) <= 0 | 小于等于 |
a[i] | a.get(i) | 索引取值 |
a[i] = b | a.set(i, b) | 索引赋值 |
a in b | b.contains(a) | 包含 |
a !in b | !b.contains(a) | 不包含 |
其他
| 操作符 | 函数 | 说明 |
|---|---|---|
a() | a.invoke() | 函数调用 |
a(b) | a.invoke(b) | 带参数调用 |
a(b, c) | a.invoke(b, c) | 多参数调用 |
小结
本章介绍了 Kotlin 操作符重载的核心内容:
- 基本概念:使用
operator关键字定义操作符函数 - 算术操作符:
+、-、*、/、%等 - 比较操作符:通过
equals和compareTo实现 - 索引操作符:
get、set、contains - 范围操作符:
rangeTo、rangeUntil - 调用操作符:
invoke让对象像函数一样调用 - 解构声明:
componentN函数支持解构
操作符重载让代码更加直观和简洁,但应保持语义一致性,避免过度使用。
练习
- 为复数类实现算术操作符
- 为自定义集合类实现索引操作符
- 实现
Comparable接口,使类支持比较操作 - 使用
invoke操作符实现一个简单的 DSL - 为 Pair 类实现解构声明功能