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将会在用到+=运算符的地方调用它。其他二元运算符也有命名相似的对应函数,如minusAssigntimesAssign

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。

通过下标来访问元素:getset

在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中一样,在它上面重复调用hasNextnext方法。

解构声明允许你展开单个复合值,并使用它来初始化多个单独的变量。事实上,解构声明再次用到了约定的原理。要在解构声明中初始化每个变量,将调用名为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类必须具有getValuesetValue方法(后者仅适用于可变属性)。想往常一样,它们可以是成员函数,也可以是扩展函数。

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函数可以用来添加属性更改的观察者。

results matching ""

    No results matching ""