03. Kotlin 运算符

算术运算符

Kotlin 中的算术运算符主要用来组织数值类型数据的算术运算,按照参加运算的操作数的不同可以分为一元运算符和二元运算符。

一元算术运算符

算术一元运算符一共有 3 个,分别是-、++和–。

运算符 名称 说明 例子
- 取反符号 取反运算 b = -a
++ 自加一 先取值再加一,或先加一再取值 aa
自减一 先取值再减一,或先减一再取值 a–或–a

二元算术运算符

二元运算符包括:+、-、*、/和%,这些运算符对数值类型数据都有效。

运算符 名称 说明 例子
+ 求 a 加 b 的和,还可用于 String 类型,进行字符串连接操作 a + b
- 求 a 减 b 的差 a - b
* 求 a 乘以 b 的积 a * b
/ 求 a 除以 b 的商 a / b
% 取余 求 a 除以 b 的余数 a % b

位运算符

位运算是以二进位(bit)为单位进行运算的,操作数和结果都是整型数据。位运算符有如下几个运算符:位反、位与、位或、位异或、有符号右移、左移和无符号右移等,具体说明参见表。

表 位运算符

运算符 名称 例子 说明
inv 位反 x.inv() 将 x 的值按位取反
and 位与 x and y 或 x.and(y) x 与 y 位进行位与运算
or 位或 x or y 或 x.or(y) x 与 y 位进行位或运算
xor 位异或 x xor y 或 x.xor(y) x 与 y 位进行位异或运算
shr 有符号右移 x shr y 或 x.shr(y) x 右移 y 位,高位采用符号位补位
shl 左移 x shl y 或 x.shl(y) x 左移 y 位,低位用 0 补位
ushr 无符号右移 x ushr a 或 x.ushr(y) x 右移 y 位,高位用 0 补位

如表所示可见 Kotlin 的位运算不是采用如 +-*/ 等特殊符号,而是使用了函数。而且除了位反 inv 和无符号右移 ushr 函数外,其他的位运算函数还可以用中缀运算符表示,中缀运算符本质上是一个函数,该函数只有一个参数。中缀运算符模拟+、-、*、/等符号运算符,函数名在中间,省略小括号。例如:

1
2
x.and(y)  // 函数表示
x and y // 中缀运算符表示

注意 无符号右移运算符仅被允许用在 Int 和 Long 整数类型,如果用于 Short 或 Byte 数据,则数据在位移之前,转换为Int 类型后再进行位移计算。

关系(比较)运算符

关系运算是比较两个表达式大小关系的运算,它的结果是布尔类型数据,即 true 或 false。关系运算符有 8 种,具体说明参见表。

运算符 名称 说明 例子
== 等于 a 等于 b 时返回 true,否则返回 false。可以应用于基本数据类型和引用类型,引用类型比较两个对象内容是否相等。==会调用 equals 函数实现比较 a == b
!= 不等于 与 == 相反 a!=b
> 大于 a 大于b时返回true,否则返回 false a>b
< 小于 a 小于b时返回true,否则返回 false a<b
>= 大于等于 a 大于等于 b 时返回 true,否则返回 false a >= b
<= 小于等于 a 小于等于 b 时返回 true,否则返回 false a <=b
=== 引用等于 用于引用类型比较,比较两个引用是否是同一个对象 a === b
!== 引用不等于 与 === 相反 a !== b

相等性检查

Kotlin 的 == 运算符和 equals 函数等同于 Java 的 equals 函数,用于结构比较。
Kotlin 中的 === 运算符等同于 Java 的 == 运算符,用于比较引用。

1
2
3
4
5
val authors = setOf("Shakespeare", "Hemingway", "Twain")
val writers = setOf("Twain", "Shakespeare", "Hemingway")

println(authors == writers) // Returns true
println(authors === writers) // Returns false

逻辑运算符

如果想清晰地表达复杂逻辑,需要用到逻辑运算符。

逻辑运算符是对布尔型变量进行运算,其结果也是布尔型。

运算符 名称 说明 例子
! 逻辑非 a 为 true 时,值为 false,a 为 false 时,值为 true !a
&& 逻辑与 ab 全为 true 时,计算结果为true,否则为 false。&& 与 & 区别:如果 a 为 false,则不计算 b(因为不论 b 为何值,结果都为false) a && b
|| 逻辑或 ab 全为 false 时,计算结果为 false,否则为 true。||| 区别:如果 a 为 true,则不计算 b(因为不论 b 为何值,结果都为true) `a

&& 和 || 都具有短路计算的特点:例如 x && y,如果 x 为 false,则不计算 y(因为不论 y 为何值,“与”操作的结果都为false);而对于x || y,如果 x 为 true,则不计算 y(因为不论 y 为何值,“或”操作的结果都为true)。

这种短路形式的设计,使它们在计算过程中就像电路短路一样采用最优化的计算方式,从而提高效率。

除了条件语句,逻辑运算符还能用于许多其他表达式,包括变量定义。

1
val auraVisible = isBlessed && healthPoints > 50 || isImmortal

其他运算符

除了前面介绍的主要运算符,kotlin 还有一些其他运算符。

  • 冒号(:)。用于变量或常量类型声明,以及声明继承父类和实现接口。
  • 小括号。起到改变表达式运算顺序的作用,它的优先级最高。
  • 中括号([])。索引访问运算符号。
  • 引用号(.)。调用函数或属性运算符。
  • 赋值号(=)。赋值是用等号运算符(=)进行的。
  • 可空符(?)。标识一个可空类型。
  • 安全调用运算符(?.)。调用非空类型的函数或属性。
  • Elvis运算符(?:)。空值合并运算符。
  • 非空断言(!!)。断言可空表达式为非空。
  • 双冒号(::)。引用类、属性或函数。
  • 区间(…)。表示一个范围区间。
  • 箭头(->)。用来声明 Lambda 表达式。
  • 展开运算符(*)。将数组传递给可变参数时使用。

除上述运算符位,还有一些鲜为人知的运算符,随着学习的深入用到后再为大家介绍,这里就不再赘述了。

区间运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun main(args: Array<String>) {
for (x in 0..5) { // 定义闭区间包含 0 和 5
print("$x,")
}
println()

for (x in 0 until 5) { // 定义半开区间包含 0,不包含 5
print("$x,")
}
println()
for (x in 'A'..'E') { // 定义闭区间包含 'A' 和 'E'
print("$x,")
}
println()

for (x in 'A' until 'E') { // 定义半开区间包含'A',不包含'E'
print("$x,")
}
}

为了处理可空数据类型,这里详细介绍如下运算符:

  • 安全调用运算符:?.
  • Elvis 运算符:?:
  • 非空断言:!!
  • 安全转换运算符:as?

安全调用运算符(?.)

1
2
3
4
5
6
7
val divNumber1 = divide(100, 0)
val result1 = divNumber1?.plus(100) // divNumber1 + 100,结果 null
println(result1)

val divNumber2 = divide(100, 10)
val result2 = divNumber2?.plus(100) // divNumber2 + 100,结果 110.0
println(result2)
1
2
3
>>> Output:
>>> null
>>> 110.0

上述代码自定义了 divide 函数进行除法运算,当参数 n2 为 0 的情况下,函数返回空值,所以函数返回类型必须是 Double 的可空类型,即 Double?。

非空断言运算符(!!)

可空类型变量可以使用非空断言运算符(!!)调用非空类型的函数或属性。非空断言运算符(!!)顾名思义就是断言可空类型变量不会为空,调用过程是存在风险的,如果可空类型变量真的为空,则会抛出空指针异常;如果非则可以正常调用函数或属性。
修改代码如下: val result1 = divNumber1!!.plus(100) // divNumber1+100,结果110.0

Elvis 运算符(?:)

有的时候在可空类型表达式中,当表达式为空值时,并不希望返回默认的空值,而是其他数值。此时可以使用 Elvis运算符(?:),也称为空值合并运算符,Elvis 运算符有两个操作数,假设有表达式:A ?: B,如果A不为空值则结果为A;否则结果为B。

Elvis 运算符经常与安全调用运算符结合使用,重写上一节示例代码如下:
val result1 = divNumber1?.plus(100) ?: 0 // divNumber1+100,结果0

Elvis 运算符由来 Elvis 一词是指美国摇滚歌手埃尔维斯·普雷斯利(Elvis Presley),绰号“猫王”。由于他的头型和眼睛很有特点,不用过多解释,从图可见为什么 ?: 叫做 Elvis 了。

in 和 !in

判断一个数值是否在区间中可以使用 in 关键字。而 !in 关键字,则是判断一个值不在区间中。此外,这两个关键字(in 和 !in)还可以判断一个数值是否集合或数组中。

运算符优先级

在一个表达式计算过程中,运算符的优先级非常重要。

如下表所示,运算符的优先级从高到低,同一行具有相同的优先级。二元运算符计算顺序从左向右,但是优先级 15 的赋值运算符的计算顺序从右向左的。

表 运算符优先级

优先级 运算符
1 小括号
2 后缀运算符 ++、–、.、?.、?
3 前缀运算符-、+、++、–、!
4 :、as、as?
5 *、/、%
6 +、-
7 区间…
8 中缀运算符
9 Elvis运算符 ?:
10 in、!in、is、!is
11 <、>、<=、>=
12 、!=、=、!==
13 &&
14 |
15 =、+=、-=、*=、/=、%=

运算符优先级顺序,从高到低是:算术运算符 → 位运算符 → 关系运算符 → 逻辑运算符 → 赋值运算符。

参考

Kotlin 从小白到大牛
https://www.ituring.com.cn/book/tupubarticle/19723?bookID=2621&type=tubook&subject=第 8 章 运算符