关于kotlin:Kotlin语言基础入门到熟悉Lambda-表达式

26次阅读

共计 5185 个字符,预计需要花费 13 分钟才能阅读完成。

什么是 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); 
 } 

kotlin 材料分享

高级 Kotlin 强化实战

140 集 Kotlin 入门到精通全系列(我的项目开发实战)视频教程

正文完
 0