在 Kotlin 开发中,我们会使用或者见到各种各样的关键字。一些比较常见,像 open
、companion
、inner
等;一些就比较冷门,像operator
、infix
、noinline
等等。这篇文章就介绍这些用得比较少得关键字。
operator
operator
关键字作用是将一个函数标记为重载一个操作符或者实现一个约定。虽然在开发过程中用到的比较少,但是实际上在 kotlin 库中,它的作用非常大。
重载操作符
在 Kotlin 协程中,CoroutineContext
就用到 operator
关键字。源码如下所示,了解即可不用关心具体的实现逻辑。
public interface CoroutineContext {
...
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {
// make sure interceptor is always last in the context (and thus is fast to get when present)
val interceptor = removed[ContinuationInterceptor]
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
}
可以看到,CoroutineContext
重载了 +
操作符,这样我们就可以在代码中使用 +
,来连接两个 CoroutineContext
了。
除了 +
操作符之外,还有很多操作符可以重载。这里分成两类,一类是数字相关的操作符,一类是集合相关的操作符。可以重载的数字操作符有:
表达式 | 对应方法 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a++ | a.inc() |
a-- | a.dec() |
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a += b | a.plusAssign(b) |
a -= b | a.minusAssign(b) |
a *= b | a.timesAssign(b) |
a /= b | a.divAssign(b) |
a %= b | a.remAssign(b) |
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null)) |
可以重载的集合操作符有:
表达式 | 对应方法 |
---|---|
a..b | a.rangeTo(b) |
a..<b | a.rangeUntil(b) |
a in b | b.contains(a) |
a !in b | !b.contains(a) |
a[i] | a.get(i) |
a[i, j] | a.get(i, j) |
a[i_1, ……, i_n] | a.get(i_1, ……, i_n) |
a[i] = b | a.set(i, b) |
a[i, j] = b | a.set(i, j, b) |
a[i_1, ……, i_n] = b | a.set(i_1, ……, i_n, b) |
自定义委托的声明
在开发过程中,我们使用 by
关键字就可以简单地实现委托功能,比如by viewModels
。但是这些常用的委托的效果,都是库中实现的。如果我们想要自定义委托的效果,就需要用到 operator
。如果你之前不了解 kotlin 的委托,可以看一文理解 Kotlin 的委托
代码示例如下:
class Example {
var p: String by Delegate()
}
class Delegate {
// operator 表明 getValue 委托对应属性的 get 方法
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return ""
}
// operator 表明 setValue 委托对应属性的 set 方法
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
}
}
对象解构
在 kotlin 中,支持对象的解构。解构是指把一个对象解构成多个变量。代码示例如下:
val (name, age) = person
如果你想使自己的类支持解构,就需要创建 componentN
方法,代码示例如下:
val name = person.component1()
val age = person.component2()
// 内部定义,需要带上 operator
operator fun component1(): String {
return name
}
operator fun component2(): Int {
return age
}
infix
带有 infix 关键字的方法,可以使用中缀表示法调用。中缀表示法是指忽略该调用的点与圆括号。代码示例如下:
class MyStringCollection {
infix fun add(s: String) { /*……*/ }
fun build() {
this add "abc" // 正确
add("abc") // 正确
//add "abc" // 错误:必须指定接收者
}
}
需要注意的是,中缀方法必须满足以下要求:
- 必须是成员函数或扩展函数。
- 必须只有一个参数。
- 其参数不得接受可变数量的参数且不能有默认值。
inline、noinline、crossinline
inline
可以让你用内联,即将方法块的代码直插到调用处,代码示例如下:
fun method(action: () -> Unit)
fun main() {
repeat(9) {
method {
doSomething()
}
}
}
// method 加了 inline后,会直接将 doSomething() 放到调用的地方
fun main() {
repeat(9) {
doSomething()
}
}
之所以需要 inline
这个关键字,是因为高阶函数实际上是通过创建对象来实现的。如果在循环中使用,会创建大量的对象,加上了inline
这个关键字就可以减少函数类型的对象的创建。
而 noinline
关键字可以局部关掉这个优化,代码示例如下:
inline fun method(action: () -> Unit, noinline result: () -> Int): () -> Int {
action()
return result
}
我们之前能够返回函数,这是因为高阶函数在 kotlin 中,是用对象实现的。如果使用了 inline
这个关键字,就不可以返回了。如果你需要局部返回,就可以使用 noinline
这个关键字来局部关闭这个优化。
最后 crossinline
关键字是局部加强这个优化,让内联函数里的函数类型的参数可以被当做对象使用。代码示例如下:
inline fun method(crossinline action: () -> Unit) {
method1 {
// 加上 crossinline 后就可以作为对象使用,但是无法返回
action()
}
// 无法作为返回值
// return action
}
fun method1(action1: () -> Unit) {
action1()
}
var、val、const、final
在 kotlin 中 var
表示是可变量,而 val
表示是只读变量,const
表示的是编译时常量,kotlin 中的 final
表示的是禁止子类覆盖。
kotlin中的 val 关键字修饰的变量与 java 中 final 修饰的变量作用是相同的。但是 val 只是表示可读变量,即该变量无法修改。但是这不意味着它不可变,如果我们自定义变量的 get 方法就可以让获取的值不一样。示例如下:
var k = 1
val j: Int
get() {
return if(k > 0) 1 else 0
}
如果要表示编译时常量,即不可变的量,需要通过 const val
来修饰的。const
只能修饰没有自定义 getter 的 val
属性,而且它的值必须在编译时确定,而且 const 只能在单例或者伴生对象中使用。
而 kotlin 中的 final
与变量和常量没有关系,它的作用是禁止子类覆盖,代码示例如下:
open class Rectangle() : Shape() {
// 禁止覆盖该方法
final override fun draw() { /*……*/ }
}
in、out、where、reified
in
、out
、where
和 reified
关键字都用在泛型中。其中:
in
表示声明处的逆变,等同于 Java 中的<? super Object>
out
表示声明处的协变,等同于 Java 中的<? extends T>
where
可以来限制泛型,等同于Java中的T extends Object
reified
为内联函数支持具体化的类型参数
其中 in
关键字除了用于泛型之外,还可以在for 循环中迭代的对象;或者作为中缀操作符,判断一个值是否属于一个区间或者一个集合。
而 reified
只能在被关键字 inline
修饰的函数中使用。代码示例如下:
// 不使用 reified 关键字
fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
var p = parent
while (p != null && !clazz.isInstance(p)) {
p = p.parent
}
@Suppress("UNCHECKED_CAST")
return p as T?
}
// 使用 reified 关键字,注意需要加上 inline
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p.parent
}
return p as T?
}
inline fun <reified T> membersOf() = T::class.members
可以看到被reified
修饰的泛型 T
可以被当作普通对象来处理。
actual、expect
actual
和 expect
是用于 kotlin 多平台的关键字。其中actual
表示多平台项目中的一个平台相关实现;而expect
将一个声明标记为平台相关,并期待在平台模块中实现。
vararg 和 *
vararg
表示可变数量的参数,等同于 Java的 method(Obj... objs)
;而 *
是展开操作符,可以将数组内容展开。代码示例如下:
fun <T> asList(vararg ts: T): List<T> {
val result = ArrayList<T>()
for (t in ts) // ts 是一个数组
result.add(t)
return result
}
val list = asList(1, 2, 3)
val a = arrayOf(1, 2, 3)
val list = asList(-1, 0, *a, 4)
tailrec
tailrec
用于尾递归的优化,Kotlin - 尾递归优化 介绍得很清楚了,具体可以看这篇文章。
typealias
typealias
关键字用来声明一个别名。代码示例如下:
// 减少命名的长度
typealias FileTable<K> = MutableMap<K, MutableList<File>>
// 为函数类型命名
typealias MyHandler = (Int, String, Any) -> Unit