Kotlin 运算符

5,734 阅读10分钟

1 一元运算符

一元运算符有前缀运算符、递增和递减运算符等。

1.1 前缀运算符

+、-、! 这三个,放在操作数的前面,它们对应的固定方法如表

运算符 对应的方法
+a a.unaryPlu()
+a a.unaryMinus()
!a a.not()

示例代码:

fun main(args: Array<String>) {
	var a = 20;
	// 使用运算符
	val b = -a;
	// 调用方法
	val c = a.unaryMinus();
	println("b: ${b}, c: ${c}");
	val flag = true
	// 使用运算符
	val notFlag1 = !flag
	// 调用方法
	val notFlag2 = flag.not()
	println("notFlag1: ${notFlag1}, notFlag2: ${notFlag2}");
}

可以看出,-a 与 a.unaryMinus()的效果是完全一样的; !flag 与 flag.not() 的效果也是完全一样的。

1.2 递增和递减运算符

自加(++)和自减(--)运算符及对应的固定方法如表

运算符 对应的方法
a++ a.inc()
a-- a.dec()

2 二元运算符

Kotlin 中的二元操作符有算术运算符、索引访问操作符、调用操作符、计算并赋值操 作符、相等与不等操作符、 Elvis操作符、比较操作符、中缀操作符等。

2.1 算术运算符

算术运算符有加、减、乘、除、取余、范围操作符等

运算符 对应的方法
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)、a.mod(b)(mod已过时)
a .. b a.rangeTo(b)

例如如下程序 。

fun main(args: Array<String>) {
	// 加法运算
	println(5 + 6)
	println(5.plus(6))
	// 乘法运算
	println(2.3 * 3.4)
	println(2.3.times(3.4))
	// 求余运算
	println(2.5 % 1.2)
	println(2.5.rem(1.2))
}

2.2 in 和 !in 运算符

in 和 !in 运算符及对应的固定方法如表

运算符 对应的方法
a in b b.contains(a)
a !in b !b.contains(a)

例如如下程序。

fun main(args: Array<String>) {
    var str = "hello java"
    // 调用 String 的 contains 方法判断
    println(str.contains("java"))
    // 使用 in 运算符判断
    println("java" in str)
    
    
    val array = arrayOf(24, 45, 100, -3, 30)
    // 调用 Array 的 contains 方法判断
    println(array.contains(100))    // 输出 true
    // 使用 in 运算符判断
    println(100 in array)    //输出 true
}

从上面代码可以看出,由于 String 类中存在带一个参数的 contains()方法,因此在 Kotlin 中即可使用 in 运算符进行计算 。

2.3 索引访问运算符

索引访问运算符及对应的固定方法如表

运算符 对应的方法
a[i] a.get(i)
a[i] = b a.set(i, b)

例如如下程序。

fun main(args: Array<String>) {
    var str = "hello java"
    // 根据 get 方法获取指定索引处的字符
    println(str.get(2))

    // 使用索引访问运算符来获取指定索引处的字符
    println(str[2])

    // 创建 Java 的 ArrayList 集合
    var list = java.util.ArrayList<String>()
    list.add("Java")
    list.add("Kotlin")
    list.add("Go")

    // 使用索引访问运算符来获取指定索引处的 List 集合元素
    println(list[1])  // 输出 Kotlin

    // 使用索引访问运算符来修改指定索引处的 List 集合元素
    list[2] = "Swift"
    println(list)
}

由于 String 类提供了 get(index)方法,因此程序即可通过由[2]这样的索引访问运算符来获取指定索引处的字符,而 Java 的 ArrayList 则提供了 get(index) 和 set(index, val)方法,因此程序可通过索引访问运算符来获取或修改指定索引处的集合元素 。

2.4 调用运算符

调用运算符及对应的固定方法如表

运算符 对应的方法
a() a.invoke()
a(i) = b a.invoke(i)

例如如下程序。

fun main(args: Array<String>) {
    val s = "java.lang.String"

    // 使用反射获取 String 类的 length ()方法
    val mtd = Class.forName(s).getMethod("length")

    // 使用传统方法,使用 Method 对象的 invoke ()方法
    println(mtd.invoke("java")) // 输出 4

    // 使用调用运算符
    println(mtd("java"))  // 输出 4
}

由于 Method 类提供了 invokeO方法,因此程序既 可按传统 Java方式调用 invoke()方法,也可直接使用调用运算符进行调用。

2.5 计算并赋值操作符

运算符 对应的方法
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)

2.6 相等与不等操作符

运算符 对应的方法
a == b a?.equals(b) ?: (b === null)
a != b !(a?.equals(b) ?: (b === null)))

Kotlin 中有两种类型的相等性:

  1. 引用相等 ===、!== (两个引用指向同一对象)
  2. 结构相等 ==、!= (使用 equals()判断)。
fun main(args: Array<String>) {
    var s1 = java.lang.String("java")
    var s2 = java.lang.String("java")
    println(s1 == s2);  // 输出 true   ①
    println(s1.equals(s2)); // 输出 true  ②
    
    println(s1 === s2); // 输出 false
    println(s1 !== s2); // 输出 true
}

上面①和②的本质是一样的 。 Java 提供的 “==” 和 “!=” 在Kotlin 中则由 “===” 和 “!==”代替了。

2.7 比较运算符

Kotlin 中所有的比较表达式都转换为对 compareTo() 函数的调用,这个函数需要返回 Int 值。

运算符 对应的方法
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

上面程序中使用比较运算符对 String、 Date两个类的实例进行了大小比较,由于这两个类都实现了 Comparable 接口,因此完全可以使用比较运算符来比较大小(至于各种数值类型,就更可以比较大小了), Kotlin 将会把大小比较运算符转换为通过 compareTo()方法进行比较。

由此可见,使用 Kotlin 编程时,只要某个类实现了 Comparable接口,那么该类的实例即可使用比较运算符来比较大小。

3 位运算符

虽然 Kotlin 也提供了与 Java 功能完全相同的位运算符,但这些位运算符都不是以特殊字 符给出的,而是以函数的形式给出的,因此程序只能用函数名来执行这些位运算符。

Kotlin支持的位运算符同样有如下7个。

  1. and: 按位与。当两位同时为 1 时才返回 1。
  2. or: 按位或。只要有一位为 1,即可返回 1。
  3. inv: 按位非。单目运算符,将操作数的每个位(包括符号位)全部取反 。
  4. xor: 按位异或。当两位相同时返回 0,不同时返回 1。
  5. shl: 左移运算符。
  6. shr: 右移运算符。
  7. ushr: 无符号右移运算符。

Kotlin 的位运算符只能对 Int 和 Long 两种数据类型起作用。

4 区间运算符

Kotlin 提供了两个区间运算符,即闭区间运算符半开区间运算符,它们都可以非常方便地构建一种数据结构,这种数据结构可包含特定区间内的所有值 。

4.1 闭区间运算符

闭区间运算符 a .. b 用于定义一个从 a~b (包括a、 b边界值)的所有值的区间。 对于闭区间运算符而言, a 不能大于 b, 否则程序运行时将会报错。

4.2 半开区间运算符

半开区间运算符 a until b 用于定义一个从 a~b (包括 a边界值,但不包含 b边界值)的所有值的区间。半开区间运算符与闭区间运算符类似, a 也不能大于 b。

如果 **a until b **中边界 a 与边界 b 的值相等,就会产生一个空区间,该区间不包含任何值;

如果 a .. b 中边界 a 与边界 b 的值相等,就会产生一个只包含一个值的区间,该区间只包含一个边界值。

fun main(args: Array<String>) {
    // 使用闭区间运算符定义区间,for-in循环可用于遍历区间内的所有数据
    var range1 = 2..4
    for (num in range1) {
        println("${num} * 5 = ${num * 5}")
    }

    // 定义数组
    val books = arrayOf("Swift", "Kotlin", "C", "C++")
    
    // 使用半开区问运算符定义区间,利用半开区间遍历数组等列表
    // (数组元素的索引是 0 到长度减 1)时非常方便
    for (index in 0 until books.size) {
        println("第 ${index + 1} 种语言是 :${books[index]}")
    }
}

输出:

2 * 5 = 10

3 * 5 = 15

4 * 5 = 20


第 1 种语言是 :Swift

第 2 种语言是 :Kotlin

第 3 种语言是 :C

第 4 种语言是 :C++

4.3 反向区间

如果程序希望区间可以从大到小,则可使用 downTo 运算符,该运算符同样构建一个闭区间 。 对于 a downTo b而言,此时要求 b 不能大于 a。 例如,如下程序使用 downTo定义了反向区间。

fun main(args: Array<String>) {
    // 使用反向闭区间运算符定义区间
    var range1 = 3 downTo 1
    for (num in range1) {
        println("${num} * 5 = ${num * 5}")
    }
}

输出:

3 * 5 = 15
2 * 5 = 10
1 * 5 = 5

4.4 区间步长

通过 step 运算符可以显式指定区间的步长。例如如下程序

fun main(args: Array<String>) {
    // 为反向闭区间指定步长
    for (num in 7 downTo 1 step 3) {
        println("${num} * 5 = ${num * 5}")
    }
}

输出:

7 * 5 = 35
4 * 5 = 20
1 * 5 = 5

5 运算符重载

Kotlin 的运算符都是靠特定名称的方法支撑的,因此只要重载这些名称的方法,我们就可以为任意类添加这些运算符。重载运算符的方法需要用 operator 修饰符进行标记。

5.1 重载一元前缀运算符

只要为任意类定义名为 unaryPlus()、 unaryMinus()、 not(),且以 operator 修饰的方法,程序即可对该类的实例使用+、-、!一元前缀运算符。 例如如下程序。

上面程序以扩展方法的形式为 Data 类定义了 一个 not() 方法,该方法同样返回一个将 Data 的 x、 y 两个属性取反的 Data 实例,这意味着我们可以对 Data 实例应用“!”这个一元前缀运算符。

上面程序中 main() 方法的最后两行对 Data 对象应用了“-”和“!”这两个运算符,这两 个表达式的输出结果是一样的。

5.2 重载自加和自减运算符

data class Data(val x: Int, val y: Int) {
    // 为 Data 类定义一个 inc ()方法
    operator fun inc(): Data {
        return Data(x + 1, y + 1)
    }
}

// 以扩展方法的形式为 Data 类定义 dee ()方法
operator fun Data.dec(): Data {
    return Data(x - 1, y - 1)
}

fun main(args: Array<String>) {
    var d = Data(4, 10)
    println(d++) // 先用,再自加,输出 Data(x=4 , y=10)
    println(d) // 输出自加后的民 Data (x=5, y=11)
    var dd = Data(9, 20)
    println(--dd) // 先自减 , 再用,输出 Data (x=8, y=19)
    println(dd) // 输出自加后的 dd: Data (x=8, y=19)
}

上面程序以普通方法和扩展方法的形式为 Data 类定义了 operator修饰了这两个方法,因此接下来程序即可对 Data 实例使用自加和自减运算符执行运算。

当程序对 Data对象使用自加运算符时,该对象的 x、 y 两个属性都会加 1;当程序对 Data 对象使用自减运算符时,该对象的 x、 y 两个属性都会减 1。至于++、-- 放在变量前面和后面的差异,其实与 Java 语言中++,--放在变量前面和后面的差异完全一样。

5.3 重载二元算术运算符

data class Point(val x: Int, val y: Int) {
    // 为 Point 类定义一个 minus ()方法
    operator fun minus(target: Point): Double {
        return Math.hypot(
            (this.x - target.x).toDouble()
            , (this.y - target.y).toDouble()
        )
    }
}

// 以扩展方法的形式为 Point 类定义 times ()方法
operator fun Point.times(target: Point): Int {
    return Math.abs(this.x - target.x) *
            Math.abs(this.y - target.y)
}

fun main(args: Array<String>) {
    var p1 = Point(4, 10)
    var p2 = Point(5, 15)
    var distance = p1 - p2;
    println("p1 与 p2 的距离为:${distance}")
    var area = p1 * p2
    println("p1 与 p2 围成矩形的面积为: ${area}")
}

输出:

pl 与 p2 的距离为:5.0990195135927845
pl 与 p2 围成矩形的面积为: 5

上面程序中定义了 一个 Point类, 接下来程序在 Point类中定义了 一个 minus()方法,该方法用于计算两个 Point 之间的距离; 此外,程序还通过扩展方法的形式为 Point 类定义了一个 times()方法,该方法用于计算两个 Point 所围成矩形的面积。

只要为 Point类定义了 minus()、 times()两个方法,接下来程序即可用 -、* 两个运算符来计算两个运算符之间的距离,以及所围成矩形的面积。

6 其他运算符

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