本文最后更新于:1 个月前

Keywrods: 函数、vararg 可变数量参数、infix 中缀表达式、tailrec 尾递归函数、内联函数 inline noinline、非局部返回、crossinline、具体化的类型参数 reified

函数

默认参数

如果⼀个默认参数在⼀个⽆默认值的参数之前,那么该默认值只能通过使⽤具名参数调⽤该函数来使⽤:

fun foo(bar: Int = 0, baz: Int) { /*……*/ }
foo(baz = 1) // 使⽤默认值 bar = 0

如果在默认参数之后的最后⼀个参数是 lambda 表达式,那么它既可以作为具名参数在括号内传⼊,也可以在括号外传⼊:

fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*……*/ }
foo(1) { println("hello") } // 使⽤默认值 baz = 1
foo(qux = { println("hello") }) // 使⽤两个默认值 bar = 0 与 baz = 1
foo { println("hello") } // 使⽤两个默认值 bar = 0 与 baz = 1

具名参数

可以通过使⽤星号操作符将可变数量参数(vararg)以具名形式传⼊:

fun foo(vararg strings: String) { /*……*/ }
foo(strings = *arrayOf("a", "b", "c"))

可变数量的参数(Varargs)

函数的参数(通常是最后一个)可以用 vararg 修饰符标记:

fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
        return result
}

在函数内部,类型 Tvararg 参数的可⻅⽅式是作为 T 数组,即上例中的 ts 变量具有类型 Array <out T>
只有⼀个参数可以标注为 vararg 。如果 vararg 参数不是列表中的最后⼀个参数,可以使⽤具名参数语法传递其后的参数的值,或者,如果参数具有函数类型,则通过在括号外部传⼀个 lambda

中缀表示法

标有 infix 关键字的函数也可以使⽤中缀表⽰法(忽略该调⽤的点与圆括号)调⽤。中缀函数必须满⾜以下要求:

  • 它们必须是成员函数或扩展函数;
  • 它们必须只有⼀个参数;
  • 其参数不得接受可变数量的参数且不能有默认值。
infix fun Int.shl(x: Int): Int { …… }
// ⽤中缀表⽰法调⽤该函数
1 shl 2
// 等同于这样
1.shl(2)

尾递归函数

val eps = 1E-10 // "good enough", could be 10^-15

tailrec fun findFixPoint(x: Double = 1.0): Double
        = if (Math.abs(x - Math.cos(x)) < eps) x else findFixPoint(Math.cos(x))
val eps = 1E-10 // "good enough", could be 10^-15

private fun findFixPoint(): Double {
    var x = 1.0
    while (true) {
        val y = Math.cos(x)
        if (Math.abs(x - y) < eps) return x
        x = Math.cos(x)
    }
}

要符合 tailrec 修饰符的条件的话,函数必须将其自身调用作为它执行的最后一个操作。在递归调用后有更多代码时,不能使用尾递归,并且不能用在 try/catch/finally 块中。

高阶函数与 lambda 表达式

带有接收者的函数字面值

内联函数

内联可能导致生成的代码增加,所以要避免内联过大函数。

非局部返回

位于 lambda 表达式中,但退出包含它的函数的返回称为非局部返回。

一些内联函数可能调用传给它们的不是直接来自函数体、而是来自另一个执行上下文的 lambda 表达式参数,例如来自局部对象或嵌套函数。在这种情况下,该 lambda 表达式中也不允许非局部控制流。为了标识这种情况,该 lambda 表达式参数需要用 crossinline 修饰符标记:

inline fun f(crossinline body: () -> Unit) {
    val f = object: Runnable {
        override fun run() = body()
    }
    // ……
}

具体化的类型参数

有时候我们需要访问一个作为参数传给我们的一个类型:

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?
}

在这里我们向上遍历一棵树并且检测每个节点是不是特定的类型。 这都没有问题,但是调用处不是很优雅:

treeNode.findParentOfType(MyTreeNode::class.java)

我们真正想要的只是传一个类型给该函数,即像这样调用它:

treeNode.findParentOfType<MyTreeNode>()

为能够这么做,内联函数支持具体化的类型参数,于是我们可以这样写:

.findParentOfType(): T? {
inline fun <reified T> TreeNode.findParentOfType(): T? {
    var p = parent
    while (p != null && p !is T) {
        p = p.parent
    }
    return p as T?
}

内联属性

inline 修饰符可用于没有幕后字段的属性的访问器。