7.运算符重载及其他约定
a+b -> a.plus(b)
| 表达式 | 函数名 | 
|---|---|
| a*b | times | 
| a/b | div | 
| a%b | mod | 
| a+b | plus | 
| a-b | minus | 
自定义类型的运算符,基本上和与标准类型的运算符有着相同的优先级。运算符*、/和%具有相同的优先级,高于+和-运算符的优先级。
从Java调用Kotlin运算符非常容易:因为每个重载的运算符都被定义为一个函数,可以像普通函数那样调用它们。当从kotlin调用Java的时候,对于与kotlin约定匹配的函数都可以使用运算符语法来调用。由于Java没有定义任何用于标记运算符函数的语法,所以使用operator修饰符的要求对它不适用,唯一的约束是,参数需要匹配名称和数量。如果Java类定义了一个满足需求的函数,但是起了一个不同的名称,可以通过定义一个扩展函数来修正这个函数名,用来代替现有的Java方法。
kotlin运算符不会自动支持交换性(交换运算符的左右两边)。如果希望用户能够使用p*1.5还能使用1.5*p,需要为它定义一个单独的运算符operator fun Double.times(p : Point) : Point
kotlin没有为标准数字类型定义任何位运算符,因此,也不允许腻味自定义类型定义他们。相反,它使用支持中缀调用语法的常规函数,可以为自定义类型定义像是的函数。
用于执行位运算的完整函数列表
| 中缀运算 | 意义 | 
|---|---|
| shl | 带符号左移 | 
| shr | 带符号右移 | 
| ushr | 无符号右移 | 
| and | 按位与 | 
| or | 按位或 | 
| xor | 按位异或 | 
| inv | 按位取反 | 
通常情况下,当你在定义像plus这样的运算符函数时,kotlin不止支持+号运算还支持+=。像+=、-=等这些运算符被称为复合赋值运算符。
如果你定义了一个返回值为Unit,名为plusAssign的函数,kotlin将会在用到+=运算符的地方调用它。其他二元运算符也有命名相似的对应函数,如minusAssign,timesAssign等
kotlin为可变集合定义了plusAssign函数
operator fun <T> MutableCollection<T>.plusAssign(elemnet : T) {
    this.add(elemnet)
}
kotlin标准库支持集合的这两种方法。+和-运算符总是返回一个新的集合。+=和-=运算符用于可变集合时,始终就地修改它们,而它们用于只读集合时,会返回一个修改过的副本。
重载一元运算符:用预先定义的一个名称来声明函数(成员函数或者扩展函数),并用operator标记。
| 表达式 | 函数名 | 
|---|---|
| +a | unaryPlus | 
| -a | unaryMinus | 
| !a | not | 
| ++a,a++ | inc | 
| --a,a-- | dec | 
与算术运算符一样,在kotlin中,可以对任何对象使用比较运算符(==、!=、>、<等),而不仅仅限于基本数据类型。
a == b --> a?.equals(b) ?: (b==null)
恒等运算符===与Java中的==运算符是完全相同的:检查两个参数是否是同一个对象的引用(如果是基本数据类型,检查它们是否是相同的值)。===运算符不能被重载。
kotlin支持相同的Comparable接口,但是接口中定义的compareTo方法可以按约定调用,比较运算符(<,>,<=,>=)的使用将被转换为compareTo
a >= b -> a.compareTo(b) >= 0
class SPerson(val firstName: String, val lastName: String) : Comparable<SPerson> {
    override fun compareTo(other: SPerson): Int {
        return compareValuesBy(this, other, SPerson::lastName, SPerson::firstName)
    }
}
val p1 = SPerson("Alice", "Smith")
val p2 = SPerson("Bob", "Johnson")
println(p1 < p2)
kotlin标准库中的compareValuesBy函数接收用来计算比较值的一系列回调,按顺序依次调用回调方法,两两一组分别做比较,并返回结果。如果值不同,则返回比较结果,如果他们相同,则继续调用下一个,如果没有更多的回调来调用,则返回0。
通过下标来访问元素:get和set
在kotlin中,下标运算符是一个约定。使用下标运算符读取元素会被转换为get运算符方法的调用,写入元素将调用set。
x[a, b] -> x.get(a, b)
x[a, b] = c -> x.set(a, b, c)
集合支持的另一个运算符是in运算符,用于检查某个对象是否属于某个集合,相应的函数叫做contains
a in c -> c.contains(a)
要创建一个区间,可以使用..语法。..运算符是调用rangeTo函数的一个简洁方法。
start..end -> start.rangeTo(end)
rangeTo函数返回一个区间,可以为自己的类定义这个运算符,如果类实现了Comparable接口,可以通过kotlin标准库创建一个任意可比较元素的区间。
operator fun <T : Comparable<T>> T.rangeTo(that : T) : ClosedRange<T>
在for循环中使用iterator的约定。在kotlin中,for循环中也可以使用in运算符,和做区间检查一样。但是在这种情况下它的含义是不同的:它被用来执行迭代。这意味着一个诸如for(x in list) {...}将被转换成list.iterator()的调用,然后就像在Java中一样,在它上面重复调用hasNext和next方法。
解构声明允许你展开单个复合值,并使用它来初始化多个单独的变量。事实上,解构声明再次用到了约定的原理。要在解构声明中初始化每个变量,将调用名为componentN的函数,其中N是声明中变量的位置。
val (a, b) = p -> val a = p.component1(); val b = p.component2()
解构声明的主要使用场景之一,是从一个函数返回多个值,这个非常有用。如果要这么做,可以定义一个数据类来保存返回所需的值,并将它作为函数的返回类型。
不可能定义无限数量的componentN函数,标准库值允许使用词语发来访问一个对象的前五个元素。
委托属性的基本语法是这样的:
class Foo {
    var p : Type by Delegate()
}
class Foo {
    private val delegate = Delegate()
    var p : Type
        set(value : Type) = delegate.setValue(..., value)
        get() = delegate.getValue()
}
按照约定,Delegate类必须具有getValue和setValue方法(后者仅适用于可变属性)。想往常一样,它们可以是成员函数,也可以是扩展函数。
class Delegate {
    operator fun getValue(...) {...}
    operator fun setValue(..., value: Type) { ... }
}
class Foo {
    var p : Type by Delegate()
}
>>> val foo = Foo()
>>> val oldValue = foo.p //通过调用delegate.getValue来实现属性的修改
>>> foo.p = newValue // 通过调用delefate.setValue(..., newValue)来实现属性的修改
class C {
    var prop: Type by MyDelegate()
}
// this code is generated by the compiler 
// when the 'provideDelegate' function is available:
class C {
    // calling "provideDelegate" to create the additional "delegate" property
    private val prop$delegate = MyDelegate().provideDelegate(this, this::prop)
    var prop: Type
        get() = prop$delegate.getValue(this, this::prop)
        set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
惰性初始化是一种常见的模式,直到第一次访问该属性时,才根据需要创建对象的一部分。当初始化过程消耗大量资源并且在使用对象时并不总是需要数据时,这个非常有用。
class Person(val name : String) {
    private var _emails : List<Email>? = null
    val emails: List<Email>
        get() {
            if(_emails == null) {
                _emails = loadEmails(this)
            }
            return _emails!!
        }
}
使用委托属性会让代码变得简单的多,可以封装用于存储值的支持属性和确保该值只被初始化一次的逻辑。在这里可以使用标准库函数lazy返回的委托。lazy标准库函数提供了实现惰性初始化属性的简单方法。
Delgates.observable函数可以用来添加属性更改的观察者。