约定,指使用与常规方法调用语法不同的、更简洁的符号,调用有着特殊命名的函数。举个例子:如果你在类中定义了一个名为plus的特殊方法(需要用operator关键字来声明该方法),那么按照约定,你就可以在该类的实例上使用+运算符。
算术运算符约定
二元算术运算符
表达式 | 函数名 |
---|---|
a + b | plus |
a - b | minus |
a * b | times |
a / b | div |
a % b | rem |
我们使用运算符时,实际上调用的是与之对应的函数。 注意事项:
- 这种自定义的运算符,基本与标准数字类型的运算符具有相同的优先级,a+b*c时会先执行乘法(times函数)。
- 不支持交换性,即a+b和b+a是不同的,如果需要支持交换性,可以定义一个额外的运算符函数。
fun main(args: Array) {
val p1 = Point(1, 10)
val p2 = Point(2, 20)
println(p1 + p2) //Point(x=3, y=30)
println(p1 - p2) //Point(x=-1, y=-10)
println(p1 * p2) //Point(x=2, y=200)
println(p1 / p2) //Point(x=0, y=0)
println(p1 % p2) //Point(x=1, y=10)
}
data class Point(private val x: Int, private val y: Int) {
operator fun plus(p: Point): Point {
return Point(x + p.x, y + p.y)
}
operator fun minus(p: Point): Point {
return Point(x - p.x, y - p.y)
}
operator fun times(p: Point): Point {
return Point(x * p.x, y * p.y)
}
operator fun div(p: Point): Point {
return Point(x / p.x, y / p.y)
}
operator fun rem(p: Point): Point {
return Point(x % p.x, y % p.y)
}
}
复合赋值运算符
表达式 | 函数名 |
---|---|
a += b | plusAssign |
a -= b | minusAssign |
a *= b | timesAssign |
a /= b | divAssign |
a %= b | remAssign |
使用方法基本同二元算术运算符,但方法必须返回Unit类型。
需要注意的是:使用+=运算符时,理论上plus和plusAssign函数都有可能被调用,即a+=b可以被转换成a=a.plus(b),也可以被转换成a.plusAssign(b)。如果类中同时定义了plus和plusAssign方法,编译器会报错。解决方法有两种:
- 不使用运算符,改用普通的函数调用。
- 使用val对象,这样plusAssign就不再适用。
一元运算符
表达式 | 函数名 |
---|---|
+a | unaryPlus |
-a | unaryMinus |
!a | not |
++a, a++ | inc |
- -a, a- - | dec |
使用方法基本同二元算术运算符,注意依然遵守前缀和后缀运算规则,前缀先改变自身再使用,后缀先使用再改变。
fun main(args: Array) {
val p1 = Point(1, 10)
val p2 = Point(2, 20)
println(-p1) //Point(x=-1, y=-10)
}
data class Point(private var x: Int, private var y: Int) {
operator fun unaryMinus(): Point {
return Point(-x, -y)
}
}
比较运算符
等号运算符
表达式 | 函数名 |
---|---|
== | equals |
!= | !equals |
==和!=运算符可以用于可空对象。 a和b相等或者a和b都为null时会返回true。
a == b 会被转换成 a?.equals(b) ?: ( b==null )
注意equals不能实现为扩展函数,因为继承自Any类的实现始终优先于扩展函数。
Kotlin另外提供了恒等运算符(===)来检查两个参数是否为同一对象的引用,效果等同于Java中的==运算符。
排序运算符
表达式 | 函数名 |
---|---|
>,<,>=,<= | compareTo |
compareTo的返回类型必须为Int。所有Java中实现了Comparable接口的类,都可以在Kotlin中使用排序运算符语法。
a >= b 会被转换成 a.compareTo(b) >= 0
fun main(args: Array) {
val p1 = Point(1, 10)
val p2 = Point(2, 20)
println(p1 > p2) //false
println(p1 < p2) //true
}
data class Point(private var x: Int, private var y: Int) {
operator fun compareTo(p: Point): Int {//也可以实现Comparable接口
return x - p.x
}
}
集合和区间的相关约定
通过下标访问元素
表达式 | 函数名 |
---|---|
x[a] | x.get(a) |
x[a,b] | x.get(a,b) |
x[a] = value | x.set(a,value) |
在Kotlin中,下标运算符是一个约定,使用下标运算符获取元素会被转换成get方法的调用,写入元素则会转换成set方法的调用。向Map和List都已经定义了这些方法,因此我们可以使用a[b]这种形式来访问和修改集合中的某个元素。
fun main(args: Array) {
val p = Point(1, 10)
println(p[1]) //10
p[1] = 20
println(p) //Point(x=1, y=20)
}
data class Point(private var x: Int, private var y: Int) {
operator fun get(index: Int): Int {
return when (index) {
0 -> x
1 -> y
else -> throw IndexOutOfBoundsException("Error")
}
}
//set方法的最后一个参数用来接收赋值语句等号右边的值,其他参数同get方法一样用来表示方括号中的下标
operator fun set(index: Int, value: Int) {
when (index) {
0 -> x = value
1 -> y = value
else -> throw IndexOutOfBoundsException("Error")
}
}
}
需要注意的是get方法的参数可以是任何类型,不只是Int,而且参数个数也可以是多个,下标运算符仅仅是get和set方法的一个简写,并不是严格意义上的下标索引。
rangeTo的约定
表达式 | 函数名 |
---|---|
a . . b | a.rangeTo(b) |
rangeTo用于创建一个闭区间,你可以为自己的类创建一个rangeTo方法来支持该运算符,或者是实现Comparable接口,因为Kotlin的标准库中已经为Comparable定义了名为rangeTo的扩展函数。
in的约定
表达式 | 函数名 |
---|---|
a in b | b.contains(a) |
in运算符用来检查某个对象是否属于区间(集合)。
fun main(args: Array) {
println(1 in 1..10) // true
println(6.6 in 1..10) // true
println(10 in 1..10) // true
println(11 in 1..10) // false
}
解构声明
结构声明允许你展开单个复合值,并使用它来初始化多个(最多5个)单独的变量。
fun main(args: Array) {
val (x, y) = Point(1,10)
println(x) //1
println(y) //10
}
要在解构声明中初始化每个变量,将会调用名为componentN的函数,其中N是声明中变量的位置,从1开始。对于数据类,编译器为每个在主构造方法中声明的非私有属性生成一个componentN函数,非数据类则需要我们自己添加。
val (a, b) = p 会被转换成 val a = p.component1() val b = p.component2()
Kotlin标准库中数组和集合已经定义了解构声明的扩展函数,比如遍历Map:
for((key, value) in map){
println("$key:$value")
}
invoke约定
如果一个类中定义了使用operator修饰符的invoke方法,就可以被当作函数一样调用该类的实例。
fun main(args: Array) {
val person = Person(18)
person("tom")
}
class Person(var age:Int){
operator fun invoke(name:String){
println("$name,$age")
}
}
与java互操作
Java调用Kotlin约定好的运算符很容易,可以直接调用与之对应的函数;Kotlin调用Java时,如果Java定义了一个与Kotlin约定相匹配的函数,Kotlin可以直接使用约定的运算符调用,Java中没有operator关键字,也不需要该关键字修饰。