本文最后更新于: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
}
在函数内部,类型 T
的 vararg
参数的可⻅⽅式是作为 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
修饰符可用于没有幕后字段的属性的访问器。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!