Kotlin语言基础入门到熟悉:Lambda 表达式

发布时间:2026/8/27 12:55:10
Kotlin语言基础入门到熟悉:Lambda 表达式 什么是 Lambda 表达式Lambda 表达式其实就是匿名函数。而函数其实就是功能function匿名函数就是匿名的功能代码了。在 Kotlin 当中函数也是作为类型的一种出现的尽管在当前的版本中函数类型的灵活性还不如 Python 这样的语言不过它也是可以被赋值和传递的这主要就体现在 Lambda 表达式上。我们先来看一个 Lambda 表达式的例子funmain(args:ArrayString){vallambda{left:Int,right:Int-leftright}println(lambda(2,3))}大家可以看到我们定义了一个变量 lambda赋值为一个 Lambda 表达式。Lambda 表达式用一对大括号括起来后面先依次写下参数及其类型如果没有就不写接着写下 - 这表明后面的是函数体了函数体的最后一句的表达式结果就是 Lambda 表达式的返回值比如这里的返回值就是参数求和的结果。后面我们用 () 的形式调用这个 Lambda 表达式其实这个 () 对应的是 invoke 方法换句话说我们在这里也可以这么写println(lambda.invoke(2,3))这两种调用的写法是完全等价的。毫无疑问这段代码的输出应该是 5。简化 Lambda 表达式我们再来看个例子funmain(args:ArrayString){args.forEach{if(itq)returnprintln(it)}println(The End)}args 是一个数组我们已经见过 for 循环迭代数组的例子不过我们其实有更现代化的手段来迭代一个数组比如上面这个例子。这没什么可怕的一旦撕下它的面具你就会发现你早就认识它了publicinlinefunTforEach(action:(T)-Unit):Unit{for(elementinthis)action(element)}这是一个扩展方法扩展方法很容易理解原有类没有这个方法我们在外部给它扩展一个新的方法这个新的方法就是扩展方法。大家都把它当做 Array 自己定义的方法就好我们看到里面其实就是一个 for 循环对吧for 循环干了什么呢调用了我们传入的Lambda表达式并传入了每个元素作为参数。所以我们调用 forEach 方法时应该怎么写呢args.forEach({element-println(element)})这相当于什么呢for(elementinargs){println(element)}很容易理解吧接着Kotlin 允许我们把函数的最后一个Lambda表达式参数移除括号外也就是说我们可以改下上面的 forEach 的写法args.forEach(){element-println(element)}看上去有点儿像函数定义了不过区别还是很明显的。这时候千万不能晕了晕了的话我这儿有晕车药吃点儿吧。事儿还没完呢如果函数只有这么一个 Lambda 表达式参数前面那个不就是么剩下一个小括号也没什么用干脆也丢掉吧args.forEach{element-println(element)}大家还好吧你以为这就结束了nonono如果传入的这个Lambda表达式只有一个参数还是比如上面这位 forEach参数只有一个 element 于是我们也可以在调用的时候省略他并且默认它叫 it说得好有道理它不就是 it 么虽然人家其实是 iterator 的意思args.forEach{println(it)}嗯差不多了。完了没没有。还有完没啊就剩这一个了。如果这个 Lambda 表达式里面只有一个函数调用并且这个函数的参数也是这个Lambda表达式的参数那么你还可以用函数引用的方式简化上面的代码args.forEach(::println)这有没有点儿像 C 里面的函数指针函数也是对象嘛没什么大惊小怪的只要实参比如 println 的入参和返回值与形参要求一致那么就可以这么简化。总结一下最后一个Lambda可以移出去只有一个Lambda小括号可省略Lambda 只有一个参数可默认为 it入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入这样我们之前给的那个例子就大致能够看懂了吧funmain(args:ArrayString){args.forEach{if(itq)returnprintln(it)}println(The End)}从 Lambda 中返回真看懂了吗假设我输入的参数是o p q r s t你知道输出什么吗o p The End对吗不对return 会直接结束 main 函数。为啥Lambda 表达式是个表达式啊虽然看上去像函数功能上也像函数可它看起来也不过是个代码块罢了。那就没办法 return 了吗当然不是兵来将挡水来土掩funmain(args:ArrayString){args.forEachforEachBlock{if(itq)returnforEachBlockprintln(it)}println(The End)}定义一个标签就可以了。你还可以在 returnforEachBlock 后面加上你的返回值如果需要的话。Lambda 表达式的类型好前面说到 Lambda 表达式其实是函数类型我们在前面的 forEach 方法中传入的 Lambda 表达式其实就是 forEach 方法的一个参数我们再来看下 forEach 的定义publicinlinefunTArrayoutT.forEach(action:(T)-Unit):Unit{for(elementinthis)action(element)}注意到action 这个形参的类型是 (T) - Unit这个是 Lambda 表达式的类型或者说函数的类型它表示这个函数接受一个 T 类型的参数返回一个 Unit 类型的结果。我们再来看几个例子()-Int//无参返回 Int(Int,Int)-String//两个整型参数返回字符串类型(()-Unit,Int)-Unit//传入了一个 Lambda 表达式和一个整型返回 Unit我们平时就用这样的形式来表示 Lambda 表达式的类型的。有人可能会说既然人家都是类型了怎么就没有个名字呢或者说它对应的是哪个类呢publicinterfaceFunctionoutR其实所有的 Lambda 表达式都是 Function 的实现这时候如果你问我那 invoke 方法呢在哪儿定义的说出来你还真别觉得搞笑Kotlin 的开发人员给我们定义了 23 个 Function 的子接口其中 FunctionN 表示 invoke 方法有 n 个参数。。publicinterfaceFunction0outR:FunctionR{publicoperatorfuninvoke():R}publicinterfaceFunction1inP1,outR:FunctionR{publicoperatorfuninvoke(p1:P1):R}...publicinterfaceFunction22inP1,inP2,inP3,inP4,inP5,inP6,inP7,inP8,inP9,inP10,inP11,inP12,inP13,inP14,inP15,inP16,inP17,inP18,inP19,inP20,inP21,inP22,outR:FunctionR{publicoperatorfuninvoke(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}说实在的第一看到这个的时候我直接笑喷了Kotlin 的开发人员还真是黑色幽默啊。这事儿不能这么完了万一我真有一个函数参数超过了 22 个难道 Kotlin 就不支持了吗funhello2(action:(Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int,Int)-Unit){action(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22)}于是我们定义一个参数有 23 个的 Lambda 表达式调用方法也比较粗暴hello2{i0,i1,i2,i3,i4,i5,i6,i7,i8,i9,i10,i11,i12,i13,i14,i15,i16,i17,i18,i19,i20,i21,i22-println($i0,$i1,$i2,$i3,$i4,$i5,$i6,$i7,$i8,$i9,$i10,$i11,$i12,$i13,$i14,$i15,$i16,$i17,$i18,$i19,$i20,$i21,$i22,)}编译运行结果Exceptioninthreadmainjava.lang.NoClassDefFoundError:kotlin/Function23 at java.lang.Class.getDeclaredMethods0(Native Method)at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)at java.lang.Class.privateGetMethodRecursive(Class.java:3048)at java.lang.Class.getMethod0(Class.java:3018)果然虽然这个参数有 23 个的 Lambda 表达式被映射成 kotlin/Function23 这个类不过这个类却不存在也就是说对于超过 22 个参数的 Lambda 表达式Kotlin 代码可以编译通过但会抛运行时异常。这当然也不是个什么事儿了毕竟有谁脑残到参数需要 22 个以上呢SAM 转换看名字挺高大上用起来炒鸡简单的东西你估计见了不少这样的东西你可千万不要回避多学会一个就能多一样拿出去唬人。valworkerExecutors.newCachedThreadPool()worker.execute{println(Hello)}本来我们应该传入一个 Runnable 的实例的结果用一个 Lambda 表达式糊弄过去GETSTATIC net/println/MainKt$main$1.INSTANCE:Lnet/println/MainKt$main$1;CHECKCAST java/lang/Runnable INVOKEINTERFACE java/util/concurrent/ExecutorService.execute(Ljava/lang/Runnable;)V你看上面的这三句字节码第一句拿到了一个类的实例这个类一看就是一个匿名内部类finalclassnet/println/MainKt$main$1implements java/lang/Runnable{...}这是这个类定义的字节码部分实现了 Runnable 接口的一个类第二句拿到这个类的实例以后做强转——还转啥直接拿来用呗肯定没问题呀。那你说 SAM 转换有什么条件呢首先调用者在 Kotlin 当中被调用者是 Java 代码。如果前面的例子当中 worker.execute(…) 是定义在 Kotlin 中方法那么我们是不能用 SAM 转换的。其次参数必须是 Java 接口也就是说Kotlin 接口和抽象类、Java 抽象类都不可以。再次参数的 Java 接口必须只有一个方法。我们再来举个 Android 中常见的例子view.setOnClickListener{view-...}view.setOnClickListener(…) 是 Java 方法参数 OnClickListener 是 Java 接口并且只有一个方法publicinterfaceOnClickListener{voidonClick(View v);}最后分享一份《史上最详Android版kotlin协程入门进阶实战》目录第一章 Kotlin协程的基础介绍1.1 协程是什么1.2 什么是Job 、Deferred 、协程作用域1.3 Kotlin协程的基础用法第二章 kotlin协程的关键知识点初步讲解2.1 协程调度器2.2 协程上下文2.3 协程启动模式2.4 协程作用域2.5 挂起函数第三章 kotlin协程的异常处理第四章 kotlin协程在Android中的基础应用4.1 Android使用kotlin协程4.2 在Activity与Framgent中使用协程4.3 ViewModel中使用协程4.4 其他环境下使用协程第五章 kotlin协程的网络请求封装5.1 协程的常用环境5.2 协程在网络请求下的封装及使用5.3 高阶函数方式5.4 多状态函数返回值方式5.5 直接返回值的方式第六章 深入kotlin协程原理一6.1 suspend 的花花肠子6.2 藏在身后的- Continuation6.3 村里的希望- SuspendLambda第七章 深入kotlin协程原理二7.1 协程的那些小秘密7.2 协程的创建过程7.3 协程的挂起与恢复7.4 协程的执行与状态机第八章 Kotlin Jetpack 实战8.1 从一个膜拜大神的 Demo 开始8.2 Kotlin 写 Gradle 脚本是一种什么体验8.3 Kotlin 编程的三重境界8.4 Kotlin 高阶函数8.5 Kotlin 泛型8.6 Kotlin 扩展8.7 Kotlin 委托第九章 Kotlin 协程 Retrofit MVVM优雅的实现网络 请求9.1 项目配置9.2 实现思路9.3 协程实现9.4 协程 ViewModel LiveData实现9.5 后续优化9.6 异常处理9.7 更新Retrofit 2.6.0