Kotlin——Lambda表达式详解
文章目录
参考
Kotlin——高级篇(一):Lambda表达式详解
Kotlin——高级篇(二):高阶函数详解与标准的高阶函数使用
掌握Kotlin标准函数:run, with, let, also and apply
Kotlin系列之Lambda表达式完全解析
Kotlin中,函数作为参数,T.()->Unit 和 ()->Unit 的区别
简述: 今天带来的Kotlin浅谈系列的第六弹, 一起来聊下Kotlin中的lambda表达式。lambda表达式应该都不陌生,在Java8中引入的一个很重要的特性,将开发者从原来繁琐的语法中解放出来,可是很遗憾的是只有Java8版本才能使用。而Kotlin则弥补了这一问题,Kotlin中的lambda表达式与Java混合编程可以支持Java8以下的版本。那我们带着以下几个问题一起来看下Kotlin中lambda表达式。
- 为什么要使用Kotlin的lambda表达式(why)?
- 如何去使用Kotlin的lambda表达式(how)?
- Kotlin的lambda表达式一般用在哪(where)?
- Kotlin的lambda表达式的作用域变量和变量捕获
- Kotlin的lambda表达式的成员引用
一、为什么要使用Kotlin的lambda表达式?
针对以上为什么使用Kotlin中的lambda表达式的问题,我觉得有三点主要的原因。
- Kotlin的lambda表达式以更加简洁易懂的语法实现功能,使开发者从原有冗余啰嗦的语法声明解放出来。可以使用函数式编程中的过滤、映射、转换等操作符处理集合数据,从而使你的代码更加接近函数式编程的风格。
- Java8以下的版本不支持Lambda表达式,而Kotlin则兼容与Java8以下版本有很好互操作性,非常适合Java8以下版本与Kotlin混合开发的模式。解决了Java8以下版本不能使用lambda表达式瓶颈。
- 在Java8版本中使用Lambda表达式是有些限制的,它不是真正意义上支持闭包,而Kotlin中lambda才是真正意义的支持闭包实现。(关于这个问题为什么下面会有阐述)
二、Kotlin的lambda表达式基本语法
1、lambda表达式分类
在Kotlin实际上可以把Lambda表达式分为两个大类,
- 一个是普通的lambda表达式
- 另一个则是带接收者的lambda表达式(功能很强大,之后会有专门分析的博客)。
这两种lambda在使用和使用场景也是有很大的不同. 先看下以下两种lambda表达式的类型声明:
针对带接收者的Lambda表达式在Kotlin中标准库函数中也是非常常见的比如with,apply标准函数的声明。
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
看到以上的lambda表达式的分类,你是不是想到之前的扩展函数了,有没有想起之前这张图?
是不是和我们之前博客说普通函数和扩展函数类似。普通的Lambda表达式类似对应普通函数的声明,而带接收者的lambda表达式则类似对应扩展函数。扩展函数就是这种声明接收者类型,然后使用接收者对象调用直接类似成员函数调用,实际内部是通过这个接收者对象实例直接访问它的方法和属性。
2、lambda基本语法
lambda的标准形式基本声明满足三个条件:
-
含有实际参数
-
含有函数体(尽管函数体为空,也得声明出来)
-
以上内部必须被包含在花括号内部
以上是lambda表达式最标准的形式,可能这种标准形式在以后的开发中可能见到比较少,更多是更加的简化形式,下面就是会介绍Lambda表达式简化规则
3、lambda语法简化转换
以后开发中我们更多的是使用简化版本的lambda表达式,因为看到标准的lambda表达式形式还是有些啰嗦,比如实参类型就可以省略,因为Kotlin这门语言支持根据上下文环境智能推导出类型,所以可以省略,摒弃啰嗦的语法,下面是lambda简化规则。
注意:语法简化是把双刃剑,简化固然不错,使用简单方便,但是不能滥用,也需要考虑到代码的可读性.上图中Lambda化简成的最简单形式用it这种,一般在多个Lambda嵌套的时候不建议使用,严重造成代码可读性,到最后估计连开发者都不知道it指代什么了。比如以下代码:
4、lambda表达式的返回值
lambda表达式返回值总是返回函数体内部最后一行表达式的值
package com.mikyou.kotlin.lambda
fun main(args: Array<String>) {
val isOddNumber = { number: Int ->
println("number is $number")
number % 2 == 1
}
println(isOddNumber.invoke(100))
}
将函数体内的两个表达式互换位置后
package com.mikyou.kotlin.lambda
fun main(args: Array<String>) {
val isOddNumber = { number: Int ->
number % 2 == 1
println("number is $number")
}
println(isOddNumber.invoke(100))
}
通过上面例子可以看出lambda表达式是返回函数体内最后一行表达式的值,由于println函数没有返回值,所以默认打印出来的是Unit类型,那它内部原理是什么呢?实际上是通过最后一行表达式返回值类型作为了invoke函数的返回值的类型
5、lambda表达式类型
Kotlin中提供了简洁的语法去定义函数的类型.
() -> Unit//表示无参数无返回值的Lambda表达式类型
(T) -> Unit//表示接收一个T类型参数,无返回值的Lambda表达式类型
(T) -> R//表示接收一个T类型参数,返回一个R类型值的Lambda表达式类型
(T, P) -> R//表示接收一个T类型和P类型的参数,返回一个R类型值的Lambda表达式类型
(T, (P,Q) -> S) -> R//表示接收一个T类型参数和一个接收P、Q类型两个参数并返回一个S类型的值的Lambda表达式类型参数,返回一个R类型值的Lambda表达式类型
上面几种类型前面几种应该好理解,估计有点难度是最后一种,最后一种实际上已经属于高阶函数的范畴。不过这里说下个人看这种类型的一个方法有点像剥洋葱一层一层往内层拆分,就是由外往里看,然后做拆分,对于本身是一个Lambda表达式类型的,先暂时看做一个整体,这样就可以确定最外层的Lambda类型,然后再用类似方法往内部拆分。
带有接收者的函数常量
Kotlin可以调用带有接收者的函数常量。在函数常量的函数体中,可以不使用任何额外限定符,调用接收者的方法,这与扩展函数相似:允许访问函数体内部接收者的成员。其中一个例子Type-safe Groovy-style builders
这种函数常量类型实际是带有接收者的函数类型
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): Unit{
val html = HTML()
html.init()
return html;
}
}
html{
body()
}
Kotlin中,函数作为参数,T.()->Unit 和 ()->Unit 的区别
在做kotlin开发中,经常看到一些系统函数里,用函数作为参数,但是又和我们自己写的不太一样
大概是这样子的:
public inline fun <T> T.apply(block: T.() -> Unit): T
{
block()
return this
}
一开始的时候,我很疑惑,我们平时定义的是这样子的啊:
fun <T : View> T.hahaha(f: () -> Unit)
{
}
这里就很疑惑了,为什么?T不是一个类吗?怎么可以直接 T.() 这是什么意思??
我们这里来看一下文档是怎么说的,
原来这里作用就是可以this代表的对象的不同。
这两个函数唯一的区别就是T.()-Unit与()->Unit的区别,我们调用时,在代码块里面写this的时候,根据代码提示,我们可以看到,这个this代表的含义不一样,T.()->Unit里的this代表的是自身实例,而()->Unit里,this代表的是外部类的实例
fun html(init: HTML.() -> Unit): HTML {
val html = HTML()
html.init()
return html
}
HTML
类实例是init
函数的调用者。也可以携带参数
fun html(init: HTML.(x: Int) -> Unit): HTML {
val html = HTML()
html.init(1)
return html
}