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 << 2 是 a shl 2;~a 是 a.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 对象,有 first、last、step、count 等属性。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)
is:类型判断,等价于 Java 的instanceof,但触发智能转换。!is:类型否定判断。as:不安全转换,类型不匹配抛ClassCastException。as?:安全转换,类型不匹配返回 null(推荐)。
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 // null9. 运算符重载(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 运算符优先级从高到低(部分):
- 后缀:
x++x--.?.!! - 前缀:
-x++x--x! - 类型转换:
asas? - 乘除模:
*/% - 加减:
+- - 区间:
.. - 中缀函数(命名):
inandshl等 - 比较:
<><=>= - 相等:
==!= - 逻辑与:
&& - 逻辑或:
|| - 赋值:
=+=等
不确定的时候用括号——比记优先级靠谱。
小结
这一章你了解了 Kotlin 的所有运算符:算术、关系、逻辑、位运算、赋值、区间、空安全、类型、运算符重载。最大的差异是位运算用中缀函数(and 而非 &),最容易踩的坑是整数除法截断。下一篇看控制流——这些运算符怎么用在条件分支和循环里。
← 上一篇 Kotlin 数据类型
下一篇 Kotlin 控制流 →