标准函数的定义通常如下属示例代码:
fun double(x: Int): Int {
return 2 * x
}
调用方式:
val result = double(2)
调用成员函数使用点表示法:
Stream().read() // 创建类 Stream 实例并调用 read()
函数参数形式:
name: type
fun powerOf(number: Int, exponent: Int): Int { /*……*/ }
函数作用域
Kotlin 函数可以在文件顶层声明,这意味着你不需要像一些语言如 Java、C# 与 Scala 那样需要创建一个类来保存一个函数。此外除了顶层函数,Kotlin 中函数也可以声明在局部作用域、作为成员函数以及扩展函数。
fun read(
b: ByteArray,
off: Int = 0,
len: Int = b.size,
) { /*……*/ }
调用时候,携带函数变量名,声明时候可赋默认值
在Compose UI 库中设置UI属性大量使用具名参数,使用具名参数可以使参数调用一目了然
fun somemethod(
str: String,
normalizeCase: Boolean = true,
wordSeparator: Char = ' ',
) { /*……*/ }
调用时
reformat(
"String!",
normalizeCase = false,
)
在Kotlin中定义了多种类型的函数
Unit函数和Java中无返回值的函数类似,Unit有点类似Void, Unit可选
fun printHello(name: String?) { …… }
当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可:
fun double(x: Int): Int = x * 2
当返回值类型可由编译器推断时,显式声明返回类型是可选的:
fun double(x: Int) = x * 2
函数的参数(通常是最后一个)可以用 vararg 修饰符标记, java中也有类似类型
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
}
在本例中,可以将可变数量的参数传递给函数:
val list = asList(1, 2, 3)
在函数内部,类型 T 的 vararg 参数的可见方式是作为 T 数组,如上例中的 ts 变量具有类型 Array
。
标有 infix 关键字的函数也可以使用中缀表示法(忽略该调用的点与圆括号)调用。中缀函数满足以下条件:
示例如下:
infix fun Int.shl(x: Int): Int { …… }
// 用中缀表示法调用该函数
1 shl 2
// 等同于这样
1.shl(2)
中缀函数调用的优先级低于算术操作符、类型转换以及 rangeTo 操作符。
Kotlin 支持局部函数,即一个函数在另一个函数内部。
fun dfs(graph: Graph) {
fun dfs(current: Vertex, visited: MutableSet<Vertex>) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v, visited)
}
dfs(graph.vertices[0], HashSet())
}
局部函数可以访问外部函数(闭包)的局部变量。在上例中,visited 可以是局部变量:
fun dfs(graph: Graph) {
val visited = HashSet<Vertex>()
fun dfs(current: Vertex) {
if (!visited.add(current)) return
for (v in current.neighbors)
dfs(v)
}
dfs(graph.vertices[0])
}
成员函数是在类或对象内部定义的函数。
函数可以有泛型参数,通过在函数名前使用尖括号指定:
fun <T> singletonList(item: T): List<T> { /*……*/ }
Kotlin 支持一种称为尾递归的函数式编程风格。
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 修饰符的条件的话,函数必须将其自身调用作为它执行的最后一个操作。
高阶函数是将函数用作参数或返回值的函数。通常使用lambda表达式方式。高阶函数的一个不错的示例是集合的函数式风格的 fold), 它接受一个初始累积值与一个接合函数,并通过将当前累积值与每个集合元素连续接合起来代入累积值来构建返回值:
fun <T, R> Collection<T>.fold(
initial: R,
combine: (acc: R, nextElement: T) -> R
): R {
var accumulator: R = initial
for (element: T in this) {
accumulator = combine(accumulator, element)
}
return accumulator
}
参数 combine
具有函数类型 (R, T) -> R
,因此 fold 接受一个函数作为参数, 该函数接受类型分别为 R 与 T 的两个参数并返回一个 R 类型的值。 在 for 循环内部调用该函数,然后将其返回值赋值给 accumulator
。
调用示例:
fun main() {
//sampleStart
val items = listOf(1, 2, 3, 4, 5)
// Lambdas 表达式是花括号括起来的代码块。
items.fold(0, {
// 如果一个 lambda 表达式有参数,前面是参数,后跟“->”
acc: Int, i: Int ->
print("acc = $acc, i = $i, ")
val result = acc + i
println("result = $result")
// lambda 表达式中的最后一个表达式是返回值:
result
})
// lambda 表达式的参数类型是可选的,如果能够推断出来的话:
val joinedToString = items.fold("Elements:", { acc, i -> acc + " " + i })
// 函数引用也可以用于高阶函数调用:
val product = items.fold(1, Int::times)
//sampleEnd
println("joinedToString = $joinedToString")
println("product = $product")
}
Kotlin 使用类似 (Int) -> String 的函数类型来处理函数的声明
声明如下:
val onClick: () -> Unit = ……
多参数函数(A, B) -> C
带有接收者函数 A.(B) -> C
挂起函数 它的表示法中有一个 suspend 修饰符suspend () -> Unit 或者 suspend A.(B) -> C
。
函数类型表示法可以选择性地包含函数的参数名:(x: Int, y: Int) -> Point
,用于表明参数含义。
函数别名 typealias ClickHandler = (Button, ClickEvent) -> Unit
函数实例化有以下几种方法:
函数类型的值可以通过其 invoke(……)
操作符调用: f.invoke(x)
或者直接 f(x)
。
如果该值具有接收者类型,那么应该将接收者对象作为第一个参数传递。 调用带有接收者的函数类型值的另一个方式是在其前面加上接收者对象, 就好比该值是一个扩展函数:1.foo(2)
。
有时使用内联函数可以为高阶函数提供灵活的控制流。内联函数可以提高代码效率,同C++中内联函数一样,编译时代码直接插入调用位置。
Kotlin中使用的高阶函数会有定的性能开销,为了消除这种开销,Kotlin引入内联的概念,让编译器在编译时做代码优化。
内联函数使用inline
修饰
inline fun
如果不希望内联所有传给内联函数的 lambda 表达式参数都内联,那么可以用 noinline 修饰符标记不希望内联的函数参数:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { …… }
lambda 表达式与匿名函数是函数字面值,函数字面值即没有声明而是立即做为表达式传递的函数。考虑下面的例子:
max(strings, { a, b -> a.length < b.length })
函数 max 是一个高阶函数,因为它接受一个函数作为第二个参数。 其第二个参数是一个表达式,它本身是一个函数,称为函数字面值,它等价于以下具名函数:
fun compare(a: String, b: String): Boolean = a.length < b.length
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
简化形式
val sum = { x: Int, y: Int -> x + y }
如果函数的最后一个参数是函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外
val product = items.fold(1) { acc, e -> acc * e }
这种语法也称为拖尾 lambda 表达式。
如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略:
run { println("...") }
一个 lambda 表达式只有一个参数很常见。
ints.filter { it > 0 } // 这个字面值是“(it: Int) -> Boolean”类型的
可以使用限定的返回语法从 lambda 显式返回一个值。 否则,将隐式返回最后一个表达式的值。
下面两段代码等价:
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
val shouldFilter = it > 0
return@filter shouldFilter
}
这一约定连同在圆括号外传递 lambda 表达式一起支持 LINQ-风格) 的代码:
strings.filter { it.length == 5 }.sortedBy { it }.map { it.uppercase() }
map.forEach { _, value -> println("$value!") }
通常函数返回值可以类型推导出来,如果确实需要显式指出,那么可以使用Lamba表达式。
fun(x: Int, y: Int): Int = x + y
除了表达式还可以是代码块
fun(x: Int, y: Int): Int {
return x + y
}
匿名函数与Lambda表达式区别:
Lambda 表达式或者匿名函数(以及局部函数和对象表达式) 可以访问其闭包 ,其中包含在外部作用域中声明的变量。 在 lambda 表达式中可以修改闭包中捕获的变量:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
有点类似扩展函数,使用this
操作接收者对象。
当接收者类型可以从上下文推断时,lambda 表达式可以用作带接收者的函数字面值。
class HTML {
fun body() { …… }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // 创建接收者对象
html.init() // 将该接收者对象传给该 lambda
return html
}
html { // 带接收者的 lambda 由此开始
body() // 调用该接收者对象的一个方法
}
在 Kotlin 中可以为类型提供预定义的一组操作符的自定义实现。这些操作符具有预定义的符号表示(如 + 或 *)与优先级。为了实现这样的操作符,需要为相应的类型提供一个指定名称的成员函数或扩展函数。这个类型会成为二元操作符左侧的类型及一元操作符的参数类型。
具体参考官方手册之运算符重载章节。