接着上一篇【Kotlin开发安卓APP笔记-函数和lambda表达式(2)】继续学习函数和lambda表达式
转自:http://blog.csdn.net/tangxl2008008/article/details/53282100
http://blog.csdn.net/tangxl2008008/article/details/53665671
高阶函数
将函数作为参数或返回一个函数,称为高阶函数。如“lock()”函数,给对象和函数提供锁功能,获取锁,执行函数,释放锁。
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
该函数的参数“body”是一个函数类型:“() -> T”,表示为一个函数,没有入参,返回一个“T”的值。
通过下面方式调用,需要传入一个函数类型参数:
fun toBeSynchronized() = sharedResource.operation()
val result = lock(lock, ::toBeSynchronized)
另外,也使用使用Lambda表示方式:
val result = lock(lock, { sharedResource.operation() })
Lambda表达式详细内容见“Lambda表达式”部分,这里先简要概述:
Ø Lambda表达一般使用“{ }”包围。
Ø 它的参数(如果有的话)在“->”前定义,参数类型可能是省略的。
Ø 函数体跟在“->”后面。
在Kotlin中,若函数最后一个参数为函数类型,调用时,该参数可以放到函数“()”的外面:
lock (lock) {
sharedResource.operation()
}
另一个高阶函数例子“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
}
可以这样调用,当只有Lambda表达式参数时,调用函数时后面的“()”也可以省略:
var ints = asList(1, 2, 3, 4)
val doubledList = ints.map { it -> it * 2 }
若函数参数对应的函数只有一个参数,在使用时,可以省略参数定义,直接使用“it”代替参数:
ints.map { it * 2 }
这种省略参数方式可以写成语言集成查询模式(LINQ-style)代码:
strings.filter { it.length == 5 }
.sortBy { it }
.map { it.toUpperCase() }
内联函数(Inline Functions)
使用高阶函数造成一些运行时问题:每一个函数都是一个对象,它会持有一个闭包;即在函数体中可以访问这些变量。内存分配(包括函数对象和类)及虚拟调用都会作为运行开销。
通过内联Lambda表达式方式,可以减少这种开销。如“lock()”函数,可以容易使用在使用位置内联相关函数,考虑下面使用方式:
fun <T> lock(lock: Lock, body: () -> T): T {
lock.lock()
try {
return body()
}
finally {
lock.unlock()
}
}
//
lock(l) { foo() }
这种使用方式,会创建一个函数对象作为参数并使用它,编译器会生成下面的代码:
l.lock()
try {
foo()
}
finally {
l.unlock()
}
内联函数,需要在函数前面使用“inline”修饰:
inline fun lock<T>(lock: Lock, body: () -> T): T {
// ...
}
使用“inline”会影响函数本身及传入的Lambda表达式参数,它们都会嵌入到它们的调用位置。
内联方式会增加生成的代码,需要合理的使用它(不要内联一个复杂功能的大函数),可以提高性能,尤其在循环中。
非内联(noinline)
有时,只需要将内联函数的部分参数使用内联Lambda,其他的参数不需要内联,可以使用“noinline”关键字修饰:
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {
// ...
}
内联Lamdbd表达式(Inlinable lambdas)只能在内联函数中或作为内联函数的参数,而非内联表达式在所有中操作,如储存到字段,传递等。
注:若一个内联函数没有任何的内联参数,并且也没有具体化类型参数(reified type parameters),编译器会抛出一个警告,提示内联函数没有实际意义(若认为内联定义时又必要的,也可以忽略该警告)。
非局部返回(Non-local returns)
在kotlin中,使用一个默认的无限制的“return”,会返回对应的函数或匿名函数。若需要返回一个Lambda,需要使用标签;由于Lambda不能直接返回一个封闭的函数,在Lambda中不允许直接使用单独的return。
fun foo() {
ordinaryFunction { //普通函数
return // ERROR: can not make `foo` return here
}
}
但是,若函数表达式是内联的,“return”是可以直接使用的:
fun foo() {
inlineFunction {
return // OK: the lambda is inlined
}
}
这类返回(位于Lambda中,但退出的是外层的封闭函数)称之为非局部返回(non-local returns)。如:
fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true // returns from hasZeros
}
return false
}
注:一些内联函数,不是直接在函数体中使用lambda参数,而是通过其他执行上下文,如具体对象或嵌套函数等。这种情况下,不能在Lambda中使用非局部返回。为了表明该种情况,可以在参数前使用“crossinline”关键字修饰标识。
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}
“break”和“continue”还不能再内联Lambd中使用;但在考虑支持他们。
具体化类型参数(Reified type parameters)
有时候,需要使用一个类型作为参数,如:
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
}
如,假设爬上一颗“tree”,使用反射去检测一个节点是否是某类型。下面方式可以实现,但不是最优方式:
myTree.findParentOfType(MyTreeNodeType::class.java)
实际上,只需要将一个类型传递给该函数;如像这种方式:
myTree.findParentOfType<MyTreeNodeType>()
为了支持这种方式,内联函数支持具体化类型参数(reified type parameters),实现方式:
inline fun <reified T> TreeNode.findParentOfType(): T? {
var p = parent
while (p != null && p !is T) {
p = p?.parent
}
return p as T
}
类型参数使用“reified”关键字修饰,就可以在函数体中访问,其他基本跟一般的类一样。若函数为内联函数,不再需要反射方式,如“!is”,“as”操作符都可以使用。可以使用下面方式使用:
myTree.findParentOfType<MyTreeNodeType>()
尽管反射在很多情况下不需要,但还是可以在具体化类型参数上使用它。
inline fun <reified T> membersOf() = T::class.members
fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}
注:普通函数(非内联函数),不能包含具体化类型参数;若一个类型没有运行时表示(run-time representation)(如非具体化类型参数(non-reified type parameter)或虚拟类型,比如“Nothing”)不能作为一个具体化类型参数的实参。
Lambda表达式和匿名函数
一个Lambda表达式或一个匿名函数 是 一个函数直接量;即函数本身是没有定义,而是通过立即当做一个函数。如下面的例子:
max(strings, { a, b -> a.length < b.length })
“max”是一个高阶函数,它的第二个参数需要一个函数。第二个参数值本身就是一个函数,即函数直接量;它等同于下面的函数:
fun compare(a: String, b: String): Boolean = a.length < b.length
函数类型(Function Types)
一个函数接收另外一个函数作为参数,需要指定该参数作为函数类型参数。如“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”,即表示入参为两个类型为“T”的参数,返回一个“Boolean”值的函数;true表示第一个值小于第二个值。
第4行将“less”当做一个函数使用。
一个函数类型可以通过上面方式实现,若想记录每个参数的意义,也可以定义成一个变量方式:
val compare: (x: T, y: T) -> Int = ...
Lambda表达式语法
Lambda表达式句法形式 就是 一个函数类型文本,如:
val sum = { x: Int, y: Int -> x + y }
一个Lambda表达式通常使用“{ }”包围,参数是定义在“()”内,可以添加类型注解,实体部分跟在“->”后面;下面为一个去掉所有的可选注解的Lambda表达:
val sum: (Int, Int) -> Int = { x, y -> x + y }
经常情况下面,Lambda表达式只有一个参数,可以不定义该参数,注解使用“it”关键字代替:
ints.filter { it > 0 } // this literal is of type '(it: Int) -> Boolean'
注:若函数的最后一个参数为函数参数,可以将Lambda表达式定义到参数列表的“()”外面。
匿名函数(Anonymous Functions)
前面的Lambda表示定义时,可以明确定义返回值类型;在大部分情况下,没有必要明确定义的,因为返回值类型基本都可以自动推断出。
需要明确定义返回值类型,也可以使用匿名函数(anonymous function)代替。
fun(x: Int, y: Int): Int = x + y
匿名函数除了省略了函数名称,其他跟一般函数的定义基本类似,函数体可以是一个表达式或其一个代码块。
fun(x: Int, y: Int): Int {
return x + y
}
上面的匿名函数的参数及返回类型跟一般函数一样,都是明确定义的;若参数类型可以通过上下文推断出来,也可以省略:
ints.filter(fun(item) = item > 0)
匿名函数的返回类型跟一般函数一样:对应只有一行执行代码的函数,编译器可以自动推断出来返回类型,可以省略;对应多方代码块的函数,需要显示定义返回值类型(为Unit可以省略)。
匿名函数 与 Lambda表示式区别:
Ø 匿名函数作为参数,一般定义在“()”中;而Lambda表达式可以定义到调用函数“()”外。
Ø 另外区别在“非局部返回(non-local returns)”行为上:非标签注解的return(返回对应的最内层的函数(即fun)),在匿名函数中,退出该匿名函数;而在Lambda表达中,退出包含该表达式的函数。
//在Lambda中使用return
fun testReturn1() {
println("testReturn1-1")
var intList = asList(1, 2, 3, 4)
println(intList)
var reusltList = intList.map {
it * 2
return
}
println(reusltList)
println("testReturn1-2")
}
//输出结果,Lambda中的return返回对对应的被包含的函数(即testReturn1()):
testReturn1-1
[1, 2, 3, 4]
//在匿名函数中使用return
fun testReturn2() {
println("testReturn2-1")
var intList = asList(1, 2, 3, 4)
println(intList)
var reusltList = intList.map(fun(item: Int): Int {
return item * 2
})
println(reusltList)
println("testReturn2-2")
}
//输出结果:
testReturn2-1
[1, 2, 3, 4]
[2, 4, 6, 8]
testReturn2-2
闭包(Closures)
Lambda表达式及匿名函数(以及局部函数,对象表达式)可以访问包含它的外部范围定义的变量(Java中只能是常量,在Kotlin中可以是变量):
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
函数文本接收器(Function Literals with Receiver)
Kotlin提供一种特殊的接收者对象( receiver object),可以访问函数文本。在函数文本的内部,可以访问接收者的成员;类似于扩展函数,在函数体中访问接收者的成员。
对接收者,函数文本相当于一个函数类型:
sum : Int.(other: Int) -> Int
//val sum : Int.(other: Int) -> Int = { this + it }
就可以当做一个函数调用:
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
}
除了手动合并了两篇,并未做任何修改,侵删