《转》Kotlin开发安卓APP笔记-函数和lambda表达式(3)

来源:互联网 发布:mac清理工具 编辑:程序博客网 时间:2024/06/13 16:06

接着上一篇【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  }  

除了手动合并了两篇,并未做任何修改,侵删