对于语言来说Lamda表达式都是轻车熟路的,但是对于Java来说从Java8才引入Lambda,所以这里还是提一下Lambda。而且Kotlin中使用Lambda的确很赞。
Lambda表达式简称lambda,本质上就是一段可以传递给其他函数的一小段代码,可以轻松的把通用的代码结构抽取成库函数。
* 函数式编程可以把函数当做值来对待。可以直接传递函数,而不需要先声明一个类再传递这个类的实例。
* 而lambda可以使代码更加简洁,都不要声明函数,可以高效的传递代码块作为参数。
fun testLambda() {
/*Java code*/ // findViewById(R.id.btn_clear).setOnClickListener(new View.OnClickListener() { // @Override // public void onClick(View v) { // clear(v); // } // }); findViewById<View>(R.id.btn_clear)?.setOnClickListener { clear() } }
可以看一段对比,体验一下使用Kotlin提供的lambda函数和不使用lambda函数库的效果
data class Book(val name: String, val page: Int) /** * 正常像Java那样中自己书写如何从集合中找到最多page的那本书 */ fun findTheMost(books: List<Book>): Book? { var maxPage = 0 var mostBook: Book? = null for (book in books) { if (book.page > maxPage) { maxPage = book.page mostBook = book } } println(mostBook) return mostBook } val books = listOf(Book("People", 50), Book("Art", 100), Book("android", 156), Book("java", 56)) /** * 使用lambda表达式来找到最多page的书 */ fun findByLambda() { // maxBy可以再任何集合上使用,且只需要一个实参: 一个函数,指定比较哪个值来找到最大元素 -> {it.age}, // 它接收一个集合中的元素作为实参(使用it 引用它),并且返回用来比较的值 println(books.maxBy { it.page }) // 如果lambda刚好是函数或者属性的委托,可以用成员引用替换 println(books.maxBy(Book::page)) }
* 一个lambda把一小段行为编进代码,你能把它当做值到处传递。它也可以被独立的声明并存储到变量中。(但更常见的还是直接声明并传递给函数使用)
* lambda 表达式语法: {x :Int , y :Int -> x+y} ,“->”前面部分是参数,后面是函数体,lambda 表达式始终在 {}中,注意参数没有使用"()"包围。
下面通过在例子中解释Kotlin 中 lambda的规则
fun testVariableLambda() {
// 声明lambda 表达式并存储在变量中 val sum = { x: Int, y: Int -> x + y } println(sum(1, 2)) // 如果确实需要把一小段代码封闭在一个代码块中,可是使用库函数run来执行传给它的lambda 如下 run { println(42) } /** 上面找最多页数的书的函数,如果不适用任何简明的语法来重写这个例子就是下面这样:*/ books.maxBy({ book: Book -> book.page }) /** Kotlin 语法约定,如果lambda表达式是函数调用的最后一个实参,它可以放到括号的外边 :*/ books.maxBy() { book: Book -> book.page } /** 当lambda 是函数唯一的实参时,还可以去掉代码中的括号对*/ books.maxBy { book: Book -> book.page } /** * 当有多个实参时,既可以把lambda留在括号内类来强调它是参数,也可以把它放到括号外面。 * 如果你想传递两个或者更过的lambda不能把超过一个的lambda放到外面!!! * */ books.joinToString(separator = " ", transform = { b: Book -> b.name }) books.joinToString(" ") { b: Book -> b.name } /**和局部变量一样,如果lambda参数的类型可以被推导出来,就不需要显示的制定它*/ books.maxBy { book -> book.name } /** * 最后简化的是使用关键字it代替命名参数。 * 如果当下下文期望的是只有一个参数的lambda且这个参数的类型可以被推导出来,就会生出这个名称(it) * 仅在实参没有显示制定名称的时候才会生成。(使用it的可读性不是很高,推荐优先命名) * */ books.maxBy { it.name } /**如果使用变量存储 lambda,就不能上下文进行推导,所以要先声明类型*/ val lambda_ = { b: Book -> b.name } books.maxBy(lambda_) /** * 如果lambda有多行语句,那么最后一行语句就是其结果 */ val lambdas = { x: Int, y: Int -> println("$x + $y = ${x + y}") x + y } //调用 println(lambdas(1, 2)) println(run { lambdas(2, 8) }) }
* 当在函数内声明一个匿名内部类的时候,能后再这个匿名内部类中引用这个函数的参数和局部变量。
* lambda 也可以访问外部函数的参数和定义在lambda之前的变量。 如:
fun printMessageWithPerfix(messages: Collection<String>, prefix: String) { messages.forEach { println("$prefix $it") } }
*这里Kotlin和Java的一个显著区别是,在Kotlin 中不仅限于访问final变量,在lambda内部还可以修改这些变量的值
* 和Java不一样Kotlin允许在lambda内部访问非final变量甚至修改它们。
* 从lambda内部访问外部变量,我们称这些变量被lambda"捕捉"。 原理大概是:(当你捕捉final变量时,它的值和使用这个值的lambda代码一起存储。中间会有封装的包装final值的包装器)
fun printErrorAndCount(messages: Collection<String>) { var clientError = 0 var serverError = 0 messages.forEach { if (it.startsWith("4")) { clientError++ } else if (it.startsWith("5")) { serverError++ } println(it) } println("messages has $clientError clientErrors and $serverError serverErrors") }
* Kotlin不仅可以把代码块作为参数传递给函数,还可以传递已经被定义好的函数
* Kotlin和Java 8一样,如果把函数转换成一个值,就可以传递它。使用"::"运算符来转换
* 这种表达式成为“成员引用”,他提供了简明的语法,来创建一个调用单个方法或者访问单个属性的函数值。
* 双冒号(::)把类名称与你想要的成员(方法或属性)名称隔开
fun tranfer() { val getPage = Book::page //上下一样, 注意不管是引用的成员还是属性都不要在成员引用的名称后面加括号。 val getPage1 = { b: Book -> b.page } //成员引用和调用该函数的lambda具有一样的类型,所以可以互换 books.maxBy(Book::page) }
* 还可以引用顶层函数(不是类的成员)
* 这种情况下可以省略类名,直接以 :: 开头
fun salute() = println("salute!") //声明顶层函数(非类的成员) fun callTopFun() { run { ::salute } // 成员引用::salute被当作实参传递给库函数run,它会调用相应的函数 run(::salute) }
“成员引用”双冒号还可以延期引用构造函数和扩展函数等..