定义函数
函数有两个Int类型的参数和Int类型的返回值。
fun sum(a : Int, b : Int) : Int{
return a + b;
}
如果函数只包含一个表达式,而且它的返回值是可推断的。
fun sum(a : Int, b : Int) = a + b
函数返回无意义的值,使用关键字Unit。
fun printMsg(a : Int, b : Int) : Unit{
println("msg of $a and $b is ${a + b}")
}
Unit关键字可以取消。
fun printMsg(a : Int, b : Int){
println("msg of $a and $b is ${a + b}")
}
函数如果存在方法体,及多个表达式时,必须显示的指定返回类型。当然如果返回的Unit时也可以不指定。
可变参数函数
局部函数
成员函数
泛型函数
inline函数
noinline函数
如果想让指定的lambda表达式inline,另一些不inline,可以使用noinline表达式。
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}
inline的lambda表达式必须在inline函数或传递inline的参数中进行调用。但是noinline函数没有这些限制,可以按照喜欢的方式进行调用。
使用时需要注意,如果一个inline函数有一个noinline参数函数和具体化的类型参数,编译器会产生一个警告,因为在这种情况下极有可能达不到inline的效果。
return语句
Kotlin中,我们可以使用return直接退出函数调用,但是退出lambda表达式,我们需要使用一个标签,并且一个单独的return是禁止的,因为lambda不能让闭包函数返回。
fun foo() {
ordinaryFunction {
return // 错误
}
}
如果传入的lambda表达式是inline的,return也会被inline,所以以下方式是允许的。
fun foo() {
inlineFunction {
return // 正确:lambda表达式inlined
}
}
以上的这种return方式被称为non-local returns。
inline函数在调用lambda表达式时可能将其作为参数而不是直接调用,而是从另外的上下文环境执行,比如本地变量或嵌套函数。在这种情况下,non-local returns是不被允许的。另外需要注意lambda参数需要使用crossinline标记。
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
}
break和continue现在还不支持,正在计划中。
具体化的类型参数
有时候我们需要访问传递给我们的参数类型。
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
}
上面的函数检查参数是否是特定的类型,虽然能使用,但是调用不优雅。
myTree.findParentOfType(MyTreeNodeType::class.java)
我们想要的仅仅是传递一个类型给函数,如下:
findParentOfType<MyTreeNodeType>()
为了实现这个,inline函数支持reified类型参数。
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p?.parent
}
return p as T
}
使用reified限制类型参数,然后就可以在函数内部访问了。就像它是一个正常的类。因为函数是inline的,就不需要反射了,取而代之是使用!is和as。普通函数(没有标记为inline)不能使用reified参数,不能再运行时保留的类型不能作为reified参数。
inline属性
inline修饰符可以用在属性存储器上(没有支持字段),你可以标记一个单独的属性存储器。
val foo: Foo
inline get() = Foo()
var bar: Bar
get() = ...
inline set(v) { ... }
可以标记所有的属性,让其标记为inline。
inline var bar: Bar
get() = ...
set(v) { ... }
在调用点,内联读写方法作为常规的内联函数内联。
高阶函数和lambda表达式
函数可以作为参数或返回值,这种函数被称为高阶函数。
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
上述代码中:() -> T是函数类型,代表无参数的函数并且返回值是T,它会在try-block中执行。
调用lock()方法,我们可以传递另一个函数作为参数。
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
或者使用lambda表达式:
val result = lock(lock, { sharedResource.operation() })
为了能够是内容继续下去,简单介绍一下lambda表达式:
- lambda表达式总是定义在大括号内
- 它的参数(如果有的话)被定义在->之前,参数类型可以忽略。
- 主体部分在->之后
在Kotlin中,如果一个函数的最后一个参数是函数,你可以传入一个lambda表达式,你可以在调用前指定它。
高阶函数中的map()函数:
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
val result = arrayListOf<R>()
for (item in this)
result.add(transform(item))
return result
}
val doubled = ints.map { value -> value * 2 }
从上述例子中可以看出,如果lambda表达式是仅有的参数,那么调用函数的括号是可以去掉的。
it是单个参数的隐式名字,如果函数只有一个参数,它的定义(包括->)可以取消。
ints.map { it * 2 }
另一个例子
strings.filter { it.length == 5 }.sortBy { it }.map { it.toUpperCase() }
如果lambda中的参数未被使用,你可以使用下划线替换它的名字。
map.forEach { _, value -> println("$value!") }
lambda表达式和匿名函数
max(strings, { a, b -> a.length < b.length })
max函数的第二个参数将lambda表达式的值作为参数。在传入的第二个参数中,其本身就是一个函数,其等价于:
fun compare(a: String, b: String): Boolean = a.length < b.length
函数类型
为了在一个函数中接受另一个函数作为参数,我们需要指定函数的类型以满足调用函数的参数要求。比如上面所述的max原型为:
fun <T> max(collection: Collection<T>, less: (T, T) -> Boolean): T? {
var max: T? = null
for (it in collection)
if (max == null || less(max, it))
max = it
return max
}
less参数的类型为:(T, T) -> Boolean,意思是参数类型为两个参数的函数并且返回Boolean。
在代码的第四行,less被用作函数进行调用。
函数类型可以按照上述方式书写,也可以有其他的名字,如果你想提供更有意义的名字。
val compare: (x: T, y: T) -> Int = ...
Lambda语法
lambda表达式的全写形式如下:
val sum = { x: Int, y: Int -> x + y }
lambda表达式总是在大括号中,参数在圆括号中并且可以有额外的注解,主体在->之后。如果返回类型不为Unit,那么lambda表达式的最后一个表达式将作为返回值。如果去掉额外的注解信息,那么它的定义看起来将会是下面的样子:
val sum: (Int, Int) -> Int = { x, y -> x + y }
lambda表达式只有一个参数是很常见的,如果Kotlin可以在函数外指定签名,那么将允许我们不定义这个参数,并且会隐式的定义这个参数,它的名字叫it。
ints.filter { it > 0 } // 类型 '(it: Int) -> Boolean',可以计算出来
显示的返回值可以使用return语句或是lambda表达式的最后一个表达式,所以下面两种方式是等价的。
ints.filter {
val shouldFilter = it > 0
shouldFilter
}
ints.filter {
filer@ val shouldFilter = it > 0
return@filter shouldFilter
}
匿名函数
在上述的lambda表达式中,缺少了返回值类型的内容。在大多数情况下,这是非必须的。因为返回类型可以推断出来。然而,如果你需要显示的指定,你可以使用匿名函数。
fun(x: Int, y: Int): Int = x + y
匿名函数没有名字,它的主体部分可以是单个表达式或者方法块。
fun(x: Int, y: Int): Int {
return x + y
}
匿名函数中的返回类型的指定与其他函数一直,但是如果参数类型可以推断出来,那么参数类型可以忽略。
ints.filter(fun(item) = item > 0)
匿名函数的返回类型可以根据表达式主体推断或是显示的指定。
需要注意的是匿名函数的参数总是需要传入的,只有lambda表达式的参数在特定情况下可以忽略。
另外一个与lambda表达式不同的地方是:没有使用标签的return语句将会返回匿名函数本身,而在lambda表达式中,它将会结束函数调用。
Closures(闭包)
lambda表达式或匿名函数以访问它的闭包。比如:外部范围定义的变量。与Java不同的是,闭包中的变量是可以修改的。
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
函数文本接收器
Kotlin提供一种特殊的接收对象,叫做函数文本(function literals),在函数文本的内部,可以访问接收者的成员;类似于扩展函数,在函数体中访问接收者的成员。
函数文本的类型就是接收者的类型:
sum : Int.(other: Int) -> Int
函数文本可以调用就像它是接收者的一个方法。
1.sum(2)
也可以使用匿名函数方式:
val sum = fun Int.(other: Int): Int = this + other
使用Lambda表达式方式:
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // create the receiver object
html.init() // pass the receiver object to the lambda
return html
}
html { // lambda with receiver begins here
body() // calling a method on the receiver object
}