首页 > 其他分享 >Kotlin语言基础入门到熟悉:Lambda 表达式

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

时间:2023-11-03 16:02:38浏览次数:37  
标签:协程 Int Kotlin println 表达式 Lambda

什么是 Lambda 表达式?

Lambda 表达式,其实就是匿名函数。而函数其实就是功能(function),匿名函数,就是匿名的功能代码了。在 Kotlin 当中,函数也是作为类型的一种出现的,尽管在当前的版本中,函数类型的灵活性还不如 Python 这样的语言,不过它也是可以被赋值和传递的,这主要就体现在 Lambda 表达式上。

我们先来看一个 Lambda 表达式的例子:

fun main(args: Array<String>) { 
     val lambda = { 
         left: Int, right: Int 
         -> 
         left + right 
     } 
     println(lambda(2, 3)) 
 }

大家可以看到我们定义了一个变量 lambda,赋值为一个 Lambda 表达式。Lambda 表达式用一对大括号括起来,后面先依次写下参数及其类型,如果没有就不写,接着写下 -> ,这表明后面的是函数体了,函数体的最后一句的表达式结果就是 Lambda 表达式的返回值,比如这里的返回值就是参数求和的结果。

后面我们用 () 的形式调用这个 Lambda 表达式,其实这个 () 对应的是 invoke 方法,换句话说,我们在这里也可以这么写:

println(lambda.invoke(2,3))

这两种调用的写法是完全等价的。

毫无疑问,这段代码的输出应该是 5。

简化 Lambda 表达式

我们再来看个例子:

fun main(args: Array<String>) { 
     args.forEach { 
        if(it == "q") return 
        println(it) 
     } 
     println("The End") 
 }

args 是一个数组,我们已经见过 for 循环迭代数组的例子,不过我们其实有更现代化的手段来迭代一个数组,比如上面这个例子。这没什么可怕的,一旦撕下它的面具,你就会发现你早就认识它了:

public inline fun <T> forEach(action: (T) -> Unit): Unit { 
     for (element in this) action(element) 
 }

这是一个扩展方法,扩展方法很容易理解,原有类没有这个方法,我们在外部给它扩展一个新的方法,这个新的方法就是扩展方法。大家都把它当做 Array 自己定义的方法就好,我们看到里面其实就是一个 for 循环对吧,for 循环干了什么呢?调用了我们传入的Lambda表达式,并传入了每个元素作为参数。所以我们调用 forEach 方法时应该怎么写呢?

args.forEach({ 
     element -> println(element) 
 })

这相当于什么呢?

for(element in args){ 
    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 的入参和返回值与形参要求一致,那么就可以这么简化。

总结一下:

  1. 最后一个Lambda可以移出去
  2. 只有一个Lambda,小括号可省略
  3. Lambda 只有一个参数可默认为 it
  4. 入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入

这样我们之前给的那个例子就大致能够看懂了吧:

fun main(args: Array<String>) { 
     args.forEach { 
        if(it == "q") return 
        println(it) 
     } 
     println("The End") 
 }
从 Lambda 中返回

真看懂了吗?假设我输入的参数是

o p q r s t

你知道输出什么吗?

o 
 p 
 The End

对吗?

不对,return 会直接结束 main 函数。为啥?Lambda 表达式,是个表达式啊,虽然看上去像函数,功能上也像函数,可它看起来也不过是个代码块罢了。

那,就没办法 return 了吗?当然不是,兵来将挡水来土掩:

fun main(args: Array<String>) { 
     args.forEach forEachBlock@{ 
        if(it == "q") return@forEachBlock 
        println(it) 
     } 
     println("The End") 
 }

定义一个标签就可以了。你还可以在 return@forEachBlock 后面加上你的返回值,如果需要的话。

Lambda 表达式的类型

好,前面说到 Lambda 表达式其实是函数类型,我们在前面的 forEach 方法中传入的 Lambda 表达式其实就是 forEach 方法的一个参数,我们再来看下 forEach 的定义:

public inline fun <T> Array<out T>.forEach(action: (T) -> Unit): Unit { 
     for (element in this) action(element) 
 }

注意到,action 这个形参的类型是 (T) -> Unit,这个是 Lambda 表达式的类型,或者说函数的类型,它表示这个函数接受一个 T 类型的参数,返回一个 Unit 类型的结果。我们再来看几个例子:

() -> Int //无参,返回 Int  
 (Int, Int) -> String //两个整型参数,返回字符串类型 
 (()->Unit, Int) -> Unit //传入了一个 Lambda 表达式和一个整型,返回 Unit

我们平时就用这样的形式来表示 Lambda 表达式的类型的。有人可能会说,既然人家都是类型了,怎么就没有个名字呢?或者说,它对应的是哪个类呢?

public interface Function<out R>

其实所有的 Lambda 表达式都是 Function 的实现,这时候如果你问我,那 invoke 方法呢?在哪儿定义的?说出来你还真别觉得搞笑,Kotlin 的开发人员给我们定义了 23 个 Function 的子接口,其中 FunctionN 表示 invoke 方法有 n 个参数。。

public interface Function0<out R> : Function<R> { 
     public operator fun invoke(): R 
 } 
 public interface Function1<in P1, out R> : Function<R> { 
     public operator fun invoke(p1: P1): R 
 } 
 ... 
 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> { 
     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 
 }

说实在的,第一看到这个的时候,我直接笑喷了,Kotlin 的开发人员还真是黑色幽默啊。

这事儿不能这么完了,万一我真有一个函数,参数超过了 22 个,难道 Kotlin 就不支持了吗?

fun hello2(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,") 
 }

编译运行结果:

Exception in thread "main" java.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 转换

看名字挺高大上,用起来炒鸡简单的东西你估计见了不少,这样的东西你可千万不要回避,多学会一个就能多一样拿出去唬人。

val worker = Executors.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

你看上面的这三句字节码,第一句拿到了一个类的实例,这个类一看就是一个匿名内部类:

final class net/println/MainKt$main$1 implements java/lang/Runnable  { 
    ... 
 }

这是这个类定义的字节码部分,实现了 Runnable 接口的一个类!

第二句,拿到这个类的实例以后做强转——还转啥,直接拿来用呗,肯定没问题呀。

那你说 SAM 转换有什么条件呢?

  • 首先,调用者在 Kotlin 当中,被调用者是 Java 代码。如果前面的例子当中 worker.execute(...) 是定义在 Kotlin 中方法,那么我们是不能用 SAM 转换的。
  • 其次,参数必须是 Java 接口,也就是说,Kotlin 接口和抽象类、Java 抽象类都不可以。
  • 再次,参数的 Java 接口必须只有一个方法。

我们再来举个 Android 中常见的例子:

view.setOnClickListener{ 
    view -> 
    ... 
 }

view.setOnClickListener(...) 是 Java 方法,参数 OnClickListener 是 Java 接口,并且只有一个方法:

public interface OnClickListener { 
     void onClick(View v); 
 }

最后分享一份《史上最详Android版kotlin协程入门进阶实战》,

目录

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

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

第一章 Kotlin协程的基础介绍

  • 1.1 协程是什么
  • 1.2 什么是Job 、Deferred 、协程作用域
  • 1.3 Kotlin协程的基础用法

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

第二章 kotlin协程的关键知识点初步讲解

  • 2.1 协程调度器
  • 2.2 协程上下文
  • 2.3 协程启动模式
  • 2.4 协程作用域
  • 2.5 挂起函数

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

第三章 kotlin协程的异常处理

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

第四章 kotlin协程在Android中的基础应用

  • 4.1 Android使用kotlin协程
  • 4.2 在Activity与Framgent中使用协程
  • 4.3 ViewModel中使用协程
  • 4.4 其他环境下使用协程

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

第五章 kotlin协程的网络请求封装

  • 5.1 协程的常用环境
  • 5.2 协程在网络请求下的封装及使用
  • 5.3 高阶函数方式
  • 5.4 多状态函数返回值方式
  • 5.5 直接返回值的方式

第六章 深入kotlin协程原理(一)

  • 6.1 suspend 的花花肠子
  • 6.2 藏在身后的- Continuation
  • 6.3 村里的希望- SuspendLambda

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

第七章 深入kotlin协程原理(二)

  • 7.1 协程的那些小秘密
  • 7.2 协程的创建过程
  • 7.3 协程的挂起与恢复
  • 7.4 协程的执行与状态机

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

第八章 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语言基础入门到熟悉:Lambda 表达式_java_09

第九章 Kotlin + 协程 + Retrofit + MVVM优雅的实现网络 请求

  • 9.1 项目配置
  • 9.2 实现思路
  • 9.3 协程实现
  • 9.4 协程 + ViewModel + LiveData实现
  • 9.5 后续优化
  • 9.6 异常处理
  • 9.7 更新Retrofit 2.6.0

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

标签:协程,Int,Kotlin,println,表达式,Lambda
From: https://blog.51cto.com/u_16163452/8172319

相关文章

  • C# Lambda 分组排序问题(先对数据进行时间倒序排列,然后再按照某字符分组,在每个分组内再
    问题:先对数据进行时间倒序排列,然后再按照某字符分组,在每个分组内再按照某数字或字符正序排列解答:vardata=list.OrderByDescending(i=>i.Date).ToList();vargData=data.GroupBy(g=>g.code).Select(l=>l.OrderBy(i=>i.Step));varinvData=newList<IndexVM>();......
  • lambda,map,filter
    1.LambdaLambda函数是一种匿名函数,它可以在一行内定义,并通常用于需要简单函数的地方。Lambda函数使用lambda关键字后跟参数列表和冒号,然后在冒号后面定义函数的主体。例如:add=lambdax,y:x+yprint(add(2,3))#输出5Lambda函数通常用于传递给高阶函数(如map、filter......
  • Python 正则表达式(RegEx)指南
    正则表达式(RegEx)是一系列字符,形成了一个搜索模式。RegEx可用于检查字符串是否包含指定的搜索模式。RegEx模块Python中有一个内置的包叫做re,它可以用于处理正则表达式。导入re模块:importrePython中的RegEx,一旦导入了re模块,您就可以开始使用正则表达式了。示例:搜索......
  • Python 正则表达式(RegEx)指南
    正则表达式(RegEx)是一系列字符,形成了一个搜索模式。RegEx可用于检查字符串是否包含指定的搜索模式。RegEx模块Python中有一个内置的包叫做re,它可以用于处理正则表达式。导入re模块:importrePython中的RegEx,一旦导入了re模块,您就可以开始使用正则表达式了。示例:搜索......
  • 正则表达式语法大全
     正则表达式基本符号:^表示匹配字符串的开始位置(例外用在中括号中[]时,可以理解为取反,表示不匹配括号中字符串)$表示匹配字符串的结束位置*表示匹配零次到多次+表示匹配一次到多次(至少有一次)?表示匹配零次或一次.表示匹配单个字符|表示为或者,两项中取一项()......
  • 表达式
    后缀表达式后序遍历。遇到数字就入栈。遇到序号——栈顶和次栈顶出栈。计算顺序:次顶元素——操作符——栈顶元素。最后得到计算结果,计算入栈重复上述过程。......
  • Qt 中的正则表达式
    Qt中的正则表达式常用QRegExp类一、正则表达式的常用匹配符^test:匹配字符的开始[^test]:表示除t,e,s,t以外的字符$:表示匹配字符串的结束[0-9]:表示0到9之间的数字*:表示匹配前面的字符0次或多次,如a*表示匹配0次或多次a字符,[0-9]*表示匹配数字0次或多次+:匹配前面的......
  • Kotlin语言基础入门:Kotlin的常用写法
    Kotlin的常用写法1.方法参数的默认值可以给方法的参数指定默认值funsomeFunction(a:Int=0,b:String=""){/*方法实现*/}2.过滤列表找出列表中满足某个条件的所有元素。使用filter方法。其中x是自己定义的参数名。vallist=Arrays.asList(1,2,3,4,5,6,7)val......
  • 正则表达式
    1.1正则表达式的概念及演示在Java中,我们经常需要验证一些字符串,例如:年龄必须是2位的数字、用户名必须是8位长度而且只能包含大小写字母、数字等。正则表达式就是用来验证各种字符串的规则。它内部描述了一些规则,我们可以验证用户输入的字符串是否匹配这个规则。先看一个不使用正则......
  • python__re模块&正则表达式*正则表达式练习题
    1、如下图是python中的re模块的讲解,已经正则表达式的基础知识2、python正则表达式训练题(网上找的,最后附上了地址)1.\d,\W,\s,\B,.,*,+,?分别是什么?\d:数字\W:非英文数字下划线的任意字符\s:任意空白字符\B:非单词开始或结尾的位置.表示非换行符的任意字符*表示匹配前面子表达式0次或多次......