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