Kotlin 运算符

运算符是语言的"基本动作"——加减乘除、比较、逻辑判断都靠它。Kotlin 的运算符大部分和 Java/JS 一致,但有一些重要差异(尤其是位运算和空安全运算符)。本篇逐一讲解。

1. 算术运算符

基础加减乘除、取余,和绝大多数语言一致:

// 算术运算符:+ - * / %  (和 Java/JS 一致)
println(7 + 3)         // 10
println(7 - 3)         // 4
println(7 * 3)         // 21
println(7 / 3)         // 2  (整数除法,截断)
println(7 / 3.0)       // 2.3333...  (转浮点)
println(7 % 3)         // 1  (取余)

// 注意:整数除法会【截断】
println(5 / 2)         // 2 (不是 2.5)
println(-5 / 2)        // -2 (向 0 取整)

// Augmented assignment (复合赋值)
var n = 5
n += 3                  // 等同 n = n + 3
n -= 2
n *= 4
n /= 5
println(n)              // 4

// ++ 和 -- 也支持
var i = 0
i++                    // 后自增,先返回再 +1
++i                    // 前自增,先 +1 再返回
i--
--i

注意整数除法的截断行为:5 / 2 等于 2 而非 2.5。要浮点结果至少一个操作数是浮点:5 / 2.0

2. 关系运算符

比较运算符返回 Boolean,行为符合直觉:

// 关系运算符:返回 Boolean
println(3 > 2)         // true
println(3 < 2)         // false
println(3 >= 3)        // true
println(3 <= 2)        // false
println(3 == 3)        // true (值相等)
println(3 != 3)        // false

// == 是【值相等】,调用 equals()
// === 是【引用相等】,判断两个引用是否指向同一对象
val a = "hello"
val b = "hello"
println(a == b)        // true (内容相同)
println(a === b)       // 通常 true (字符串池),不保证

val list1 = listOf(1, 2)
val list2 = listOf(1, 2)
println(list1 == list2)  // true (内容相同)
println(list1 === list2) // false (不同对象)

关键差异:Kotlin 的 == 等价于 Java 的 equals()(值相等),而非引用相等。这避免了 Java 初学者最常见的坑("a" == new String("a") 在 Java 里是 false,但在 Kotlin 里恒为 true)。

3. 逻辑运算符

逻辑与(&&)、或(||)、非(!)都有短路特性:

// 逻辑运算符(&& || !)
val a = true
val b = false
println(a && b)        // false (短路:第一个 false 就不再算第二个)
println(a || b)        // true  (短路:第一个 true 就不再算第二个)
println(!a)            // false
println(!b)            // true

// 短路的实际用处:避免 NPE
val name: String? = null
if (name != null && name.length > 0) {
    println(name)
}

// Kotlin 没有 & 和 | (位运算符和逻辑运算符不混淆)
// 详见下面的位运算

Kotlin 不像 C/Java 还有单字符 & | (非短路逻辑运算符)——Kotlin 把 & | 留给位运算(其实是函数 and or),逻辑运算只有 && || ! 这三种。

4. 位运算符(关键差异!)

Kotlin 的位运算不用符号,而是用中缀函数——这是和 Java/C 的关键差异:

// 位运算符:Kotlin 用中缀函数,不用符号
// shl  = 左移 (<<)
// shr  = 右移 (>>),符号位填充
// ushr = 无符号右移 (>>>)
// and  = 按位与 (&)
// or   = 按位或 (|)
// xor  = 按位异或 (^)
// inv  = 按位取反 (~)

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())       // -13 (按位取反)
println(a shl 2)       // 48  (左移 2 位)
println(a shr 1)       // 6   (右移 1 位)

对比 Java:a & b 在 Kotlin 是 a and b;a << 2a shl 2;~aa.inv()。设计理由:符号重载容易混淆(用户看不出 & 是逻辑还是位),中缀函数语义更清晰,而且可以重载。

5. 赋值运算符

= 是简单赋值,复合赋值(+= -= *= /= %=)都有:

// 赋值运算符:= 和复合赋值
var x = 10
x += 5         // x = x + 5 = 15
x -= 3         // x = x - 3 = 12
x *= 2         // x = x * 2 = 24
x /= 5         // x = x / 5 = 4 (整数除法)
x %= 3         // x = x % 3 = 1

// 注意:Kotlin 不支持自增自减出现在表达式里(只能作语句)
// val y = x++        // 错误
// 必须:
x++             // 作语句
val y = x       // 再赋值

注意 Kotlin 不允许自增自减作为表达式(只能作语句),即 val y = x++ 是错的——必须先 x++val y = x。这避免了 C 家族里 i++ + ++i 这种歧义表达式的坑。

6. 区间运算符(in、..、until、downTo、step)

这是 Kotlin 的甜点特性,写循环和判断非常顺手:

// 区间运算符:.. 和 until 和 downTo 和 step
println(1..5)              // 1..5 (闭区间 [1, 5])
println(1 until 5)         // 1..4 (半开区间 [1, 5))
println(5 downTo 1)        // 5..1 (递减)
println(1..10 step 2)      // 1, 3, 5, 7, 9 (步长 2)

// 用 in 判断元素是否在区间
val score = 85
if (score in 0..59) {
    println("不及格")
} else if (score in 60..84) {
    println("及格")
} else if (score in 85..100) {
    println("优秀")
}

// !in 反向判断
val age = 17
if (age !in 18..60) {
    println("不在工作年龄")
}

// 字符区间
for (c in 'a'..'e') print(c)    // abcde

区间本质上是一个 IntRange 对象,有 firstlaststepcount 等属性。in 实际调用 contains(),任何实现了 contains 的类都支持。

7. 空安全运算符(招牌特性)

Kotlin 区分可空和非空类型,提供一组运算符处理 null:

// 空安全运算符:Kotlin 的招牌(详见专门章节)
var name: String? = "小明"

// 1. ?. 安全调用:name 为 null 时整个表达式为 null
println(name?.length)         // 2

// 2. ?: Elvis 运算符:为 null 时给默认值
val len = name?.length ?: 0   // 2 (或 0 如果 name 为 null)

// 3. !! 非空断言:为 null 抛 NPE (慎用!)
println(name!!.length)        // 2

// 4. as? 安全转换:类型不匹配返回 null
val x: Any = "hello"
val n: Int? = x as? Int       // null (因为是 String)

// 5. ?.let { } 非空时执行块
name?.let {
    println("名字是 $it")
}

这些运算符详见 专门的空安全章节,是 Kotlin 比 Java 的最大改进。

8. 类型运算符(is、as)

val obj: Any = "hello"

// is: 类型判断
if (obj is String) {
    println(obj.length)        // 智能转换,obj 当 String 用
}

// !is: 否定判断
if (obj !is Int) {
    println("不是 Int")
}

// as: 不安全转换(失败抛异常)
val s: String = obj as String

// as?: 安全转换(失败返回 null)
val n: Int? = obj as? Int     // null

9. 运算符重载(operator fun)

Kotlin 允许给自定义类定义运算符行为,通过 operator fun 实现:

// 运算符重载:Kotlin 允许给自定义类定义运算符
data class Vector2(val x: Int, val y: Int) {
    operator fun plus(other: Vector2) = Vector2(x + other.x, y + other.y)
    operator fun times(scalar: Int) = Vector2(x * scalar, y * scalar)
}

val v1 = Vector2(1, 2)
val v2 = Vector2(3, 4)
println(v1 + v2)              // Vector2(x=4, y=6)
println(v1 * 3)               // Vector2(x=3, y=6)

// 可重载的运算符(operator fun):
// + - * / % += -=  ..  in  []  ()  rangeTo  contains  get  set  invoke  compare  等

可重载的运算符对应一组固定函数名:plus(+)、minus(-)、times(*)、div(/)、get([])、set([]=)、contains(in)、rangeTo(..)、compare(< >)、invoke(()) 等。完整列表见官方文档。

运算符优先级

Kotlin 运算符优先级从高到低(部分):

不确定的时候用括号——比记优先级靠谱。

小结

这一章你了解了 Kotlin 的所有运算符:算术、关系、逻辑、位运算、赋值、区间、空安全、类型、运算符重载。最大的差异是位运算用中缀函数(and 而非 &),最容易踩的坑是整数除法截断。下一篇看控制流——这些运算符怎么用在条件分支和循环里。

← 上一篇 Kotlin 数据类型

下一篇 Kotlin 控制流

✈️💬