Kotlin学习笔记(四)约定(一)

约定,指使用与常规方法调用语法不同的、更简洁的符号,调用有着特殊命名的函数。举个例子:如果你在类中定义了一个名为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关键字,也不需要该关键字修饰。

你可能感兴趣的:(Kotlin学习笔记(四)约定(一))