Kotlin第八讲---Lambda表达式

内容简介

上一篇我们讲解了函数定义丶特点丶实现原理。这一篇我们主讲一下 Kotlin 中的 Lambda 表达式和它的本质。Lambda 早在 Java 中就存在了,但是我们一般都视而不见。Kotlin 中 Lambda 表达式扮演着很重要的角色,所以必须搞明白哦!

定义Lambda表达

定义一个 Lambda 表达式,主要使用 {} 来定义,下方的代码是定义一个再简单不过的 Lambda 表达式了。


   
   
  1. /**

  2. * 定义一个 Lambda 并将其赋值给一个变量

  3. * 无参的

  4. */

  5. val lambda = {}

  6. fun main() {

  7. /**

  8. * 调用定义的 Lambda 表达式

  9. * 通过 () 调用或调用 invoke 方法

  10. */

  11. lambda()

  12. lambda.invoke()

  13. }

定义有参Lambda表达式

其实定义有参的 Lambda 只需要通过 {参数1:类型,参数2:类型->} 定义即可(特别注意有一个 -> 小箭头哦)。


   
   
  1. /**

  2. * 定义一个 Lambda 并将其赋值给一个变量

  3. * 有参的 lambda 表达式

  4. */

  5. val lambda = { name: String, age: Int ->

  6. println("$name $age")

  7. }

  8. fun main() {

  9. /**

  10. * 调用定义的 Lambda 表达式

  11. * 通过 () 调用或调用 invoke 方法

  12. * 传参

  13. */

  14. lambda("阿文",18)

  15. lambda.invoke("awen",18)

  16. }

定义具有返回值的Lambda表达式

Lambda 表达式的返回值和一般函数不一样,一般函数是通过 return 来确定。而 Lambda 表达式是最后一句代码的返回值来确定(注意:只有 Lambda 才有此功能,普通函数乖乖的写 return 吧)。


   
   
  1. /**

  2. * 定义一个具有返回值的 Lambda 表达式

  3. */

  4. val sum = { v1: Int, v2: Int ->

  5. v1 + v2

  6. }

  7. fun main() {

  8. val value = sum(1, 2)

  9. println("1 + 2 = $value")

  10. }

这里补充一点:若我们的 Lambda 表达式最后一句代码的返回值,不是表达式要返回的或本身表达式没有返回值,要如何处理呢?其实我们只需要在 Lambda 表达式末尾,写入要返回的变量即可。


   
   
  1. val call = { v1: Int, v2: Int ->

  2. v1 + v2

  3. // 最后一句代码返回 Unit 变量,代表没有返回值

  4. Unit

  5. }

  6. fun main() {

  7. call(1, 2)

  8. }

Lambda使用

在上一章中,我们提到过一个很重要的特性,在 Kotlin 中可以将函数以参数的形式传递,还记得为何能传递吗?

因为传递函数的本质是传递了一个实现 FunctionN 接口的一个对象。细心的同学也许注意到了,我们定义的 Lambda 表达式都存放到了一个变量当中,既然能存在变量当中,那么 Lambda 表达式肯定也可以来回传递的。


   
   
  1. /**

  2. * 我们用Android 中最常见的回调来举例

  3. *

  4. * 定义成功的 lambda 表达式

  5. */

  6. val success = { success: String ->

  7. println(success)

  8. }

  9. /**

  10. * 定义出错的 lambda 表达式

  11. */

  12. val error = { error: String ->

  13. println(error)

  14. }

  15. /**

  16. * 定义调用方法

  17. */

  18. fun call(success: (String) -> Unit, error: (String) -> Unit) {

  19. if (Random(20).nextInt() % 2 == 0) {

  20. success("成功")

  21. } else {

  22. error("出错")

  23. }

  24. }

  25. fun main() {

  26. // 调用并传入2个 Lambda 表达式

  27. call(success, error)

  28. }

上面的代码,我将 2 个 Lambda 表达式以参数的形式传递给了 call 函数 。其实一般我们不这样定义,还记得我讲过 Kotlin 中可以支持函数嵌套吗(大家可以回顾下原理)?
我们一般这样写:


   
   
  1. /**

  2. * 定义调用方法

  3. */

  4. fun call(success: (String) -> Unit, error: (String) -> Unit) {

  5. if (Random(20).nextInt() % 2 == 0) {

  6. success("成功")

  7. } else {

  8. error("出错")

  9. }

  10. }

  11. fun main() {

  12. // 调用并传入2个 Lambda 表达式

  13. call({

  14. println(it)

  15. }, { error ->

  16. println(error)

  17. })

  18. }

看到这里,大家应该注意2个问题。

  1. 调用的时候 call 方法的时候,嵌套定义的 Lambda 表达式,怎么没定义变量, it 又是个什么东西?

  2. call 方法定义的行参中的类型 (String)->Unit 是啥意思?上一章节中传递函数类型使用的不是 FunctionN 类型吗?

这里我解释下第一个问题,函数只需要一个参数的 Lambda 表达式的时候,定义的 Lambda 可以不写这个参数(当然你想写也可以,例如我的 error ),第一个参数用 it来代替。

至于第二个问题,请看下一小节。

通过本质看Lambda

上一篇中,我们讲过函数当做参数传递,本质是传递了一个实现 FunctionN 的对象,那 Lambda 和 FunctionN 有什么关系呢?以及我们前面举例的 (String)->Unit 到底是什么?
我们来看下下面的代码:


   
   
  1. /**

  2. * 定义调用方法

  3. * 注意我将 success & error Function1对象

  4. */

  5. fun call(success: Function1<String, Unit>, error: Function1<String, Unit>) {

  6. if (Random(20).nextInt() % 2 == 0) {

  7. success("成功")

  8. } else {

  9. error("出错")

  10. }

  11. }

  12. fun main() {

  13. // 调用并传入2个 Lambda 表达式

  14. call({

  15. println(it)

  16. }, { error ->

  17. println(error)

  18. })

  19. }

是不是发现了什么?类型是 FunctionN 的对象,可以直接定义对应参数个数和返回值的 Lambda 即可。那是不是说定义的 Lambda 表达式最终会编译成一个实现 FunctionN 的接口对象呢(和上一篇中的函数指针类似)?
我们来看下反编译结果:


   
   
  1. public final class DemoKt {

  2. public static final void call(@NotNull Function1<? super String, Unit> success, @NotNull Function1<? super String, Unit> error) {

  3. ...

  4. if (RandomKt.Random(20).nextInt() % 2 == 0) {

  5. success.invoke("成功");

  6. } else {

  7. error.invoke(“出错”);

  8. }

  9. }

  10. public static final void main() {

  11. // 可以看到定义的 Lambda 表达式,最终生成了2个类 DemoKt$main$1 和 DemoKt$main$2

  12. call(DemoKt$main$1.INSTANCE, DemoKt$main$2.INSTANCE);

  13. }

  14. }

可以看到定义的 Lambda 表达式,最终生成了2个类 DemoKt$main$1 和 DemoKt$main$2,我们看下 DemoKt$main$1 的源码,果然没错的确是实现了 Function1 接口。


   
   
  1. final class DemoKt$main$1 extends Lambda implements Function1<String, Unit> {

  2. public static final DemoKt$main$1 INSTANCE = new DemoKt$main$1();

  3. DemoKt$main$1() {

  4. super(1);

  5. }

  6. public /* bridge */ /* synthetic */ Object invoke(Object obj) {

  7. invoke((String) obj);

  8. return Unit.INSTANCE;

  9. }

  10. public final void invoke(@NotNull String it) {

  11. System.out.println(it);

  12. }

  13. }

到现在我们明白了 Lambda 表达式最终会编译成实现 FunctionN 的类。接下来我们去看下 FunctionN 接口的定义。


   
   
  1. /** A function that takes 0 arguments. */

  2. public interface Function0<out R> : Function<R> {

  3. /** Invokes the function. */

  4. public operator fun invoke(): R

  5. }

  6. /** A function that takes 1 argument. */

  7. public interface Function1<in P1, out R> : Function<R> {

  8. /** Invokes the function with the specified argument. */

  9. public operator fun invoke(p1: P1): R

  10. }

  11. ..... 中间还有多 Function0 到 Function22

  12. /** A function that takes 22 arguments. */

  13. public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {

  14. /** Invokes the function with the specified arguments. */

  15. public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R

  16. }

我们可以看到定义了超级多的 FunctionN 的接口,对应 N 代表的是有多少个参数,也就是说定义的 Lambda 表达式最多支持 22 个参数,接下来验证下超过 23 个 Lambda 表达式会怎样呢?经过我测试执行下方代码正常,我很诧异,为啥?不是只定义到了 Function22 吗?


   
   
  1. /**

  2. * 定义超过23个参数的lambda表达式

  3. */

  4. val test =

  5. { va1: Int, va2: Int, va3: Int, va4: Int, va5: Int, va6: Int, va7: Int,

  6. va8: Int, va9: Int, va10: Int, va11: Int, va12: Int, va13: Int, va14:

  7. Int, va15: Int, va16: Int, va17: Int, va18: Int, va19: Int, va20: Int,

  8. va21: Int, va22: Int, va23: Int ->

  9. }

  10. fun main() {

  11. // 测试调用

  12. test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23)

  13. }

看下源码,发现既然还真有一个 FunctionN 的接口存在


   
   
  1. final class DemoKt$test$1 extends Lambda implements FunctionN<Unit> {

  2. public static final DemoKt$test$1 INSTANCE = new DemoKt$test$1();

  3. DemoKt$test$1() {

  4. super(23);

  5. }

  6. public Object invoke(Object[] objArr) {

  7. Object[] objArr2 = objArr;

  8. if (objArr2.length != 23) {

  9. // 如果数组的长度不是23个就报错

  10. Intrinsics.throwIllegalArgument("");

  11. }

  12. invoke(((Number) objArr2[0]).intValue(), ((Number) objArr2[1]).intValue(), ((Number) objArr2[2]).intValue(), ((Number) objArr2[3]).intValue(), ((Number) objArr2[4]).intValue(), ((Number) objArr2[5]).intValue(), ((Number) objArr2[6]).intValue(), ((Number) objArr2[7]).intValue(), ((Number) objArr2[8]).intValue(), ((Number) objArr2[9]).intValue(), ((Number) objArr2[10]).intValue(), ((Number) objArr2[11]).intValue(), ((Number) objArr2[12]).intValue(), ((Number) objArr2[13]).intValue(), ((Number) objArr2[14]).intValue(), ((Number) objArr2[15]).intValue(), ((Number) objArr2[16]).intValue(), ((Number) objArr2[17]).intValue(), ((Number) objArr2[18]).intValue(), ((Number) objArr2[19]).intValue(), ((Number) objArr2[20]).intValue(), ((Number) objArr2[21]).intValue(), ((Number) objArr2[22]).intValue());

  13. return Unit.INSTANCE;

  14. }

  15. public final void invoke(int va1, int va2, int va3, int va4, int va5, int va6, int va7, int va8, int va9, int va10, int va11, int va12, int va13, int va14, int va15, int va16, int va17, int va18, int va19, int va20, int va21, int va22, int va23) {

  16. }

  17. }

其实也能想到, FunctionN 的 invoke 方法参数是一个可变参数就行了。


   
   
  1. interface FunctionN<out R> : Function<R>, FunctionBase<R> {

  2. operator fun invoke(vararg args: Any?): R

  3. override val arity: Int

  4. }

到现在我们就剩下一个问题了,前面写的 (String)->Unit 是啥意思?其实大家猜也应该能猜到,这种写法只是定义 FunctionX(X代表数字)的另外一种写法, Kotlin 的设计师们认为 FunctionX 的方式不直观,便有了这种比较直观的写法,编译器最终还是会编译成 FunctionX

表达式FunctionX
(String) -> UnitFunction1<String,Unit>
(String,Int) -> DoubleFunction2<String,Int,Double>
。。。。后面的自行推导。。。。后面的自行推导

Lambda的特殊情况

若函数最后一个参数是一个 Lambda 表达式,可以将 Lambda 写在括号外面。若函数的参数只有一个 Lambda 表达式,括号都不用写了。


   
   
  1. /**

  2. * 定义最后一个参数是 `Lambda` 类型

  3. */

  4. fun call(name: String, block: (String) -> Unit) {

  5. }

  6. /**

  7. * 定义只有一个参数并且是 `Lambda` 类型

  8. */

  9. fun call2(block: (String) -> Unit) {

  10. }

  11. fun main() {

  12. // 可以将 `Lambda` 写在括号外面

  13. call("阿文"){

  14. }

  15. // 只有一个参数的,并且是 `Lambda` 类型,括号都可以不写了

  16. call2{

  17. println(it)

  18. }

  19. }

在 Kotlin 中,如果一个函数的行参是一个接口类型且接口只有一个方法(注意是接口且一个方法哦),可以直接传递一个 lambda 表达式。在 Andorid 中最常见的 View 点击事件注册。


   
   
  1. /**

  2. * 例如这样,直接就可以写

  3. */

  4. myView.setOnClickListener {

  5. }

  6. /**

  7. * 如需使用对应接口定义的参数,只需要定义的 lambda 定义参数即可。

  8. */

  9. myView.setOnClickListener { view ->

  10. }

细心的同学读源码的时候可能看到,定义 Lambda 的时候,参数通过 _ 来命名。
例如:


   
   
  1. /**

  2. * 定义最后一个参数是 `Lambda` 类型

  3. */

  4. fun call(block: (String, Int) -> Unit) {

  5. block("阿文",18)

  6. }

  7. fun main() {

  8. call { _, _ ->

  9. }

  10. }

其实这只是一个方便的操作,当一个 Lambda 有很多参数且我们都用不到这些参数,无需给其定义名字,用 _ 代替即可。

总结

我们来总结下 Lambda 的几个特点吧!

  1. Lambda 表达式的最后一句代码的返回值是表达式的返回值

  2. 若 Lambda 的参数只有一个,可以不用定义,使用 it 代替。

  3. 可以通过 (类型1,类型2)->返回值 直观快速的定义 FunctionX 的类型。

  4. Lambda 的最终会编译成一个实现 FunctionX 的接口类,并存在一个静态对象。

  5. 若函数最后一个参数是一个 Lambda 表达式,可以将 Lambda 定义写在括号外面。若函数的参数只有一个 Lambda 表达式,括号都不用写了。

  6. 一个函数行参是一个接口且接口只有一个方法,可以直接传递一个 lambda 表达式

  7. 当一个 Lambda 有很多参数,若我们都用不到这些参数,无需给其定义名字,用 _ 代替即可。

推荐阅读

-

-END--

识别二维码,关注我们

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值