Kotlin运算符重载及其他约定摘要
重载算数运算符
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point { //定义一个名为plus的方法
return Point(x + other.x, y + other.y) //坐标分别相加,然后返回一个新的点
}
}
>>> val p1 = Point(10, 20)
>>> val p2 = Point(30, 40)
>>> println(p1 + p2) //通过使用+号来调用plus方法
Point(x=40, y=60)
Kotlin 限定了你能重载哪些运算符,以及你需要在你的类里面定义的对应名字的函数,你不能定义自己的运算符。
可重载的二元算术运算符:
表达式 | 函数名 |
---|---|
a*b |
times |
a/b |
div |
a%b |
mod |
a+b |
plus |
a-b |
minus |
从 Java 调用 Kotlin 运算符非常容易:因为每个重载的运算符都被定义为一个函数,你可以像普通函数那样调用它们。 当从 Kotlin 调用 Java 的时候,对于与 Kotlin 约定匹配的函数都可以使用运算符语法来调用。 由于 Java 没有定义任何用于标记运算符函数的语法,所以使用
operator
修饰符的要求对它不适用,唯一的约束是,参数需要匹配名称和数量。
注意, Kotlin 运算符不会自动支持交换性(交换运算符的左右两边)。 如果希望用户能够使用1.5 * p
以外,还能使用p * 1.5
,你需要为它定义一个单独的运算符:operator fun Double.times(p: Point): Point
。
没有用于位运算的特殊运算符
Kotlin 没有为标准数字类型定义任何按位运算符,它使用中缀调用语法的常规函数。
以下是 Kotlin 提供的,用于执行位运算的完整函数列表:
- shl — 带符号左移
- shr — 带符号右移
- ushr — 无符号右移
- and — 按位与
- or — 按位或
- xor — 按位异或
- inv — 按位取反
下面的示例展示了这些函数的使用方法:
>>> println(0x0F and 0xF0)
0
>>> println(0x0F or 0xF0)
255
>>> println(0x1 shl 4)
16
集合运算符
Kotlin 标准库为可变集合定义了plusAssign
函数:
operator fun <T> MutableCollection<T>.plusAssign(element: T) {
this.add(element)
}
用法
>>> val numbers = ArrayList<Int>()
>>> numbers += 42
>>> println(numbers[0])
42
当你在代码里面用到+=
的时候,理论上plus
和plusAssign
都会被调用到。如果在这种情况下,两个函数都有定义且适用,编译器会报一个错误。
注意事项:
Kotlin 标准库支持集合的两种方法。+
和-
运算符总是返回一个新的集合。+=
和-=
运算符可以用于可变集合,始终在一个地方修改他们;而它们用于只读集合时,会返回一个修改过的副本。(这意味着只有当引用只读集合的变量被声明为var的时候,才能使用+=
和-=
。)
重载一元运算符
可重载的一元的算法运算符
表达式 | 函数名 |
---|---|
+a |
unaryPlus |
-a |
unaryMinus |
!a |
not |
++a, a++ |
inc |
--a, a-- |
dec |
重载比较运算符
在 Kotlin 里面,你可以对任何对象使用比较运算符(==
,!=
,>
,<
等等),而不仅仅限于基本数据类型。 不像Java一样需要调用equals
或compareTo
函数,你可以直接使用比较运算符。
注意,===
(恒等)运算符不能被重载,恒等运算符与Java中的==
运算符是完全相同的。
“get”和“set”
data class MutablePoint(var x: Int, var y: Int)
operator fun MutablePoint.set(index: Int, value: Int) {
//定义一个运算符函数名为set
when(index) {
0 -> x = value //根据给出的index修改对应的坐标
1 -> y = value
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
>>> val p = MutablePoint(10, 20)
>>> p[1] = 42
>>> println(p)
MutablePoint(x=10, y=42)
operator fun Point.get(index: Int): Int {
return when(index) { //定义一个运算符函数名为get
0 -> x //根据给出的index返回对应的坐标
1 -> y
else ->
throw IndexOutOfBoundsException("Invalid coordinate $index")
}
}
>>> val p = Point(10, 20)
>>> println(p[1])
20
“in”的约定
相应的函数叫做contains
。
operator fun Rectangle.contains(p: Point): Boolean {
return p.x in upperLeft.x until lowerRight.x &&
p.y in upperLeft.y until lowerRight.y
} //构建一个区间,检查坐标x是否属于这个区间
//使用until函数来构建一个开区间
>>> val rect = Rectangle(Point(10, 20), Point(50, 50))
>>> println(Point(20, 30) in rect)
true
>>> println(Point(5, 5) in rect)
false
rangTo的约定
..
运算符是调用rangeTo
函数的一个简洁方法。
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
解构声明
一个解构声明看起来像一个普通的变量声明,但它在括号中有多个变量。
>>> val p = Point(10, 20)
>>> val (x, y) = p //声明变量x,y,然后用p的组件来初始化
>>> println(x)
10
>>> println(y)
20
要在解构声明中初始化每个变量,将调用名为componentN
的函数,其中N
是声明中变量的位置。
-
对于数据类,编译器为每个在主构造方法中声明的属性生成一个
componentN
函数。 -
手动为非数据类声明这些功能:
class Point(val x: Int, val y: Int) { operator fun component1() = x operator fun component2() = y }
-
标准库只允许使用此语法来访问一个对象的前五个元素。
解构声明不仅可以用作函数中的顶层语句,还可以用在其他可以声明变量的地方,例如in
循环。
fun printEntries(map: Map<String, String>) {
for ((key, value) in map) { //在in循环中用解构声明
println("$key -> $value")
}
}
Kotlin 标准库给map增加了一个扩展的iterator
函数,用来返回map条目的迭代器。因此,与Java不同的是,你可以直接迭代map。
for (entry in map.entries) {
val key = entry.component1()
val value = entry.component2()
// ...
}
委托属性
基本语法
class Foo {
var p: Type by Delegate()
}
惰性初始化和"by lazy()"
惰性初始化 是一种常见的模式,直到在第一次访问时该属性的时候,才根据需要创建一部分对象
class Person(val name: String) {
val emails by lazy { loadEmails(this) }
}
默认情况下,lazy
函数是线程安全的,如果需要,你可以设置其他选项来告诉它要使用哪个锁,或者完全避开同步,如果该类永远不会在多线程环境中使用。
注意,要实现自己的可以用作委托的类,或者说实现自定义的委托属性,请参考《Kotlin 实战》一书 7.5.3节。理解代理属性的原理,并在实际编程中使用,会增加代码的简介度。