Kotlin之Lambda表达式原理及应用

来源:互联网 发布:红色痘印怎么消除 知乎 编辑:程序博客网 时间:2024/06/08 13:27

Kotlin之Lambda表达式原理及应用

初探

Lambda表达,可以看成是一个代码块,先来一个栗子热热身.

      val codeblock = { f: Float, s: String ->          println("hello $s")          f.toByte()      }
  1. 以上的codeblock就是一个代码块,也可以理解成一个方法,它的两个参数分别为Float,String
  2. 它的返回类型是Byte

val codeblock暴露了它是一种类型,那它是什么类型呢?打印它

println(codeblock.javaClass)输出:class com.seekting.kotlindemo.PersonKt$main$codeblock$1

我们可以理解成它是一个内部类,且名字叫1

那它有什么方法呢?打印它

   for (m in codeblock.javaClass.methods) {        println(m.name)    }  输出:      invoke    invoke    toString    getArity    wait    wait    wait    equals    hashCode    getClass    notify    notifyAll

你会发现比其它类多了些什么,那我新建一个Test类,看看它会有什么方法

class Test() {} val t = Test()    for (m in t.javaClass.methods) {        println("test:${m.name}")    }打印结果:test:waittest:waittest:waittest:equalstest:toStringtest:hashCodetest:getClasstest:notifytest:notifyAll

可以看出多了三个方法getArity,invoke,invoke

从这点可以看出,lambda表达就是一个类,它有invoke方法,那我们通过反射调一下吧

    for (m in codeblock.javaClass.methods) {        if (m.name == "invoke") {            val a = m.invoke(codeblock, 1.2f, "tt")            println("a=$a")        } else if (m.name == "getArity") {            val a = m.invoke(codeblock)            println("a=$a")        }    }    输出:    hello tt    a=1    hello tt    a=1    a=2

通过输出发现调两个invoke调用了代码块:

 println("hello $s")          f.toByte()

并通过代码块返回了1
那getArity返回2是几个意思年,arity是参数数量的意思,难道和参数列表有关?改代码块试试

    for (m in codeblock.javaClass.methods) {        if (m.name == "invoke") {            val a = m.invoke(codeblock, 1.2f, "tt", true)            println("a=$a")        } else if (m.name == "getArity") {            val a = m.invoke(codeblock)            println("a=$a")        }    }输出:hello tt,b=truea=1hello tt,b=truea=1a=3

果然它是参数个数的意思

总结一下,代码块可以理解成java的一个类,且有invoke方法

看看这一行代码:

val result: Byte = codeblock(1.2f, "3")

以上代码就是调用了该对象的invoke方法而已。

进阶:

我们知道一个方法,它的参数可以是任意类型,那Lambda也是一种类型(变相类型)

申明一个mn方法,它有Int m,Int n,Lambda block三个形参,返回Boolean
你可以认为block是一个类型,此类型有一个invoke方法,它接收Float,String类型的参数,返回Byte (没绕进去吧我的哥)

inline fun mn(n: Int, m: Int, block: (Float, String) -> Byte): Boolean {    block(3.14f, "PI")//其实是调用对象的invoke方法    return m * n > 0}mn(1, 2, { f: Float, s: String ->    val result = "i=${f}s=$s"    print(result)    f.toByte()//最后一行就是返回})
  1. mn为一个方法Boolean是它的返回
  2. mn有三个参数:m,n,代码块block
  3. 代码块的的返回类型是Byte类型
  4. 这个代码块需要调用的人实现,但是这个代码块依赖两个参数Float,String
  5. 实现block代码块的人会得到Float,String参数,可能通过这两个参数操作,为result服务
  6. block的Float,String两个参数由mn方法提供

解迷Standard.kt里的一些函数库

run()函数

 run {        println("run!")    }

这是系统里自带的库函数,看起来挺厉害的样子,来分析一下怎么实现的吧

@kotlin.internal.InlineOnlypublic inline fun <R> run(block: () -> R): R = block()
  1. 泛型了一个R
  2. run方法有一个变相类型(Lambda)
  3. R=block()可以理解为
{ return block.invoke()}

你一定会好奇:run方法明明有一个参数,为何不见了呢?
如果最后一个参数是block()可以用大括号括起来.我们也可以写一个这样的方法myRun

fun myRun(t: String, block: (String) -> Unit) {    block(t)} myRun("hello") {        println(it)    }

但是如果是这样呢?它要两个block,这个时候就只能是第一个block1通过传参数

fun myRun1(t: String, block: (String) -> Unit, block1: (String) -> Unit) {    block(t)} val block1 = { s: String ->        println(s)    }myRun1("hello", block1) {}

let()函数

public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
  1. T,R泛型,代表任何对象都可以调用let方法
  2. let方法有一个参数是变相类型(Lambda)参数是T,返回是R
  3. 方法实体是调用了这个Lambda的invoke方法,并把T当成参数传进去
    以下是let的例子
 "seekting".let {        println(it.get(0))    }    val person = Person1("seekting")    person.let {        it.say()        it.sleep()        it.wakeup()    }

with()函数

public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
  1. 泛型T,R
  2. 第一个参数是一个对象,第二个参数是(Lambda)该Lambda有点特别:
block: T.() -> R

T.()这个代码初看无法理解。通过IDE我偶然发现了,其实T.()是T类的方法的扩展,还记得有这样的代码

public inline fun String.toUpperCase(): String 

它扩展了String的方法,但是这个方法不是String类自己的,可以理解成

public static String toUpperCase(String input){}

我用block:Person.()来打比方,它只有一个函数print

 class Person(        var id: Long,        var name: String,        var age: Int) {    fun print() {        var ss = toString()        Log.d("seekting", ss)    }}fun study(person: Person, block: Person.() -> Unit) {    person.print()    person.block()}

但是在study方法里可以调用Person.block方法,说明什么?
说明这个Person扩展了一个方法叫block,而它的实现:

  study(person1) {        person1.age = 11        person1.name = "11"    }

你可以理解为有一个方法:

fun Person.block(){    this.age = 11    this.name = "11"}

回过头来看with

public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()with(person){    person1.age = 11    person1.name = "11"}
  1. 它有一个T类型,并扩展了一个函数叫block
  2. receiver.block()表示t调用了一个扩展了的函数(但只在with里可见)
  3. 而这个block()函数怎么实现的,外部实现的;实现体就是
person1.age = 11person1.name = "11"

apply()函数

public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
  1. 任意对象都有一个apply方法,还有一个block方法(只在apply里可见)
  2. apply方法调用block方法,block方法由外部自己实现,实现完返回该对象
  3. 对比let:任意对象(T)有一个let方法,该方法能把T对象当成参数处理并返block的返回类型
  4. 它们的共同点都会调用block代码块,但是不一样的是:let返回的是block返回类型,apply返回的是原对象
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)

also()函数,apply和also的区别

public inline fun <T> T.also(block: (T) -> Unit): T { block(this); return this }

also和apply类似,唯一不一样的是:

  1. apply是调用对象的扩展block()方法,因此在block里可以用this,而不能用it

  2. 而also是调用一个block(T t)方法,因此block里不能用this,而能用it

also 与apply的不同之处:

 person1.apply {        this.age    }person1.also {    it.age = 11}person1.also { p: Person ->    p.age = 11    //this.age=11 报错}
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 考试的时候检测仪响怎么办 吸入腐蚀性气体导致嗓子疼怎么办 孕妇已做c13检查怎么办 静电除尘器绝缘子箱温度低怎么办 高中三角函数计算总算不对怎么办 江苏高考物理考d怎么办 高二化学学不好怎么办 中考最后一次月考下滑怎么办 物联网卡网速慢怎么办 机械表长时间不带不走了怎么办 高中档案有涂改痕迹怎么办 大学平时成绩为0怎么办 电大英语考试成绩取消了怎么办 网贷评分不足要怎么办 学业水平广东1c怎么办 绣花机速度太慢怎么办 娃脖子有点烂了怎么办 7月省内流量套餐怎么办 qq手游授权失败怎么办 钉钉不够6人创建怎么办 钉钉 不够6个人怎么办 plsql删错了表怎么办 吊兰长出来的茎怎么办 防水台鞋跟太高怎么办 证件照头部比例过大怎么办 特岗照片传错了怎么办 打印报名表照片不显示怎么办 刚买的床有味道怎么办 雨刷器角度太小怎么办 四个月宝宝闹觉怎么办 怀孕六个月睡不好觉怎么办? 婴儿睡不好觉总是吵闹怎么办 婴儿鼻塞睡不好觉怎么办 玩英雄联盟鼠标变亮白怎么办 练芭蕾脚受伤了怎么办 高三了英语30分怎么办 要上高中了英语不好怎么办 高二了数学不好怎么办 高二函数不好怎么办啊 输乳怎么办腺病有什么妇症状 屁股上坐的发黑怎么办