print("求和 ")

x + y

}

val diff = { x: Int, y: Int ->

print("求差 ")

x - y

}

//kotlin里面,可以把拉姆达表示当作一个普通变量一样,去传递实参

calculate(p1 = 1, p2 = 2, event1 = sum, event2 = diff)

}

定义了一个calculate函数, p1,p2 是Int,而event1和event2 则是 lambda表达式. 高级语言特性,一等函数公民:函数本身可以被当作普通参数一样去传递,并且调用。那么, kotlin的lambda内核是怎么样的?

image

上图能够看出,

  1. calculate方法的后面2个参数,被编译成了 Function2 类型。

  2. 执行event1,event2,则是调用其invoke方法

  3. main函数中,出现了null.INSTANCE, 这里比较诡异,INSTANCE应该是用来获取实例的,但是为什么是null.INSTANCE

而看了Function2的源码,简直亮瞎了我的钛合金狗眼:

image

Function.kt文件中:

Function接口,Function2接口…Function22接口。好了,不要质疑谷歌大佬的设计思路,反正大佬写的就是对的…这里用22个接口(至于为什么是22个,猜想是谷歌觉得不会有哪个脑子秀逗的开发者真的弄22个以上的参数挤在一起吧),表示kotlin开发中可能出现的lambda表达式参数列表。

再举个栗子

给一个Button 设置点击事件,kotlin的写法是:

val btn = Button(this)

val lis = View.OnClickListener { println(“111”) }

btn.setOnClickListener(lis)

或者:

val btn = Button(this)

btn.setOnClickListener { println(“111”) }

setOnClickListener 方法的参数是 OnClickListener 接口:

public interface OnClickListener {

void onClick(View v);

}

类似这种符合lambda表达式特征的接口,都可以使用上述两种写法来大大简化代码量。

最后一个栗子

不得不提一句,lambda表达式有一个变形,那就是:当lambda表达式作为方法的最后一个参数时,可以lambda表达式放到小括号外面。而如果只有一个参数就是lambda表达式,那么括号可以省略

这个非常重要,不了解这个,很多地方都会感觉很蛋疼。

fun testLambda(s: String, block: () -> String) {

println(s)

block()

}

fun testLambda2(block: () -> String) {

block()

}

fun main() {

testLambda(“第一个参数”) {

println(“block函数体”)

“返回值”

}

testLambda2 {

println(“block函数体”)

“返回值”

}

}

总结

Kotlin中lambda表达式可以当作普通参数一样去传递,去赋值,去使用。


高阶函数以及操作符

上文提到,kotlin中lambda表达式已经融入了语言核心,而具体的体现就是高阶函数,把lambda表达式当作参数去使用. 这种将lambda表达式作为参数或者返回值的函数,就叫高阶函数。

官方高阶函数

Kotlin谷歌已经给我们封装了一些高阶函数。

  • run

  • with

  • apply

  • also

  • let

  • takeif 和 takeunless

  • repeat

  • lazy

run函数详解

代码如下(这里为了展示代码全貌,忽视androidStudio的代码优化提示):

class A {

val a = 1

val b = “b”

}

fun testRun() {

//run方法有两种用法,一个是不依赖对象,也就是作为全局函数

run {//我可以规定返回值的类型

println(“我是全局函数”)

“返回值”

}

val a = A()

//另一种则是 依赖对象

a.run<A,String> {//这里同样可以规定返回值的类型

println(this.a)

println(this.b)

“返回值”

}

}

fun main() {

testRun()

}

如上所示:

run函数分为两类

  • 不依赖对象的全局函数。

  • 依赖对象的"类似"扩展函数。

两者都可以规定返回值类型(精通泛型的话,这里应该不难理解,泛型下一节详解)。

阅读源码:

@kotlin.internal.InlineOnly

public inline fun run(block: () -> R): R {

contract {

callsInPlace(block, InvocationKind.EXACTLY_ONCE)

}

return block()

}

@kotlin.internal.InlineOnly

public inline fun <T, R> T.run(block: T.() -> R): R {

contract {

callsInPlace(block, InvocationKind.EXACTLY_ONCE)

}

return block()

}

run函数被重载,参数有所不同

  • 前者 参数类型为 ()->R ,返回值为 R ,函数体内执行block(),并且返回执行结果

  • 后者 参数类型为 T.()->R ,返回值为R , T.() 明显是依赖 T类的(貌似T的扩展函数),返回值依然是R,执行完之后返回结果。

  • 并且,可以发现 两者都是内联函数 inline (执行代码时不进行方法的压栈出栈,而是类似于直接在目标处执行代码段)

所以,前者不需要依赖对象,后者必须依赖对象(因为它是T类的"扩展函数")

使用场景

根据run方法的特性,无论是不是依赖对象,它都是封装了一段代码,而且还是inline的。所以:

  • 如果你不想把一段代码独立成方法,又不想让它们看上去这么凌乱,最重要的是告诉后来的开发者 这段代码是一个整体,不要随便给我在里面插代码,或者拆分。那就用run方法,把他们整合到一个作用域中。

run {

println(“这是一段代码逻辑很相近的代码段”)

println(“但是我不想把他独立成一个方法”)

println(“又担心别人会随便改”)

println(“所以用run方法把他们放在同一个作用域中”)

println(“小组中其他人看到这段,就知道不要把无关代码插进来”)

}

  • 更神奇的是,这个run函数是有返回值的,参数返回值可以利用起来:

fun testRun2(param1: String, param2: String, param3: String) {

//我想让这几个参数都不为空,如果检查是空,就不执行方法主体

val checkRes: Boolean = run {

when {

param1.isNullOrEmpty() -> {

false

}

param2.isNullOrEmpty() -> {

false

}

param3.isNullOrEmpty() -> {

false

}

else -> true

}

}

if (checkRes){

println(“参数检查完毕,现在可以继续接下来的操作了”)

}else{

println(“参数检查不通过,不执行主体代码”)

}

}

fun main() {

testRun2(“1”, “2”, “”)

}

main运行结果:

参数检查完毕,现在可以继续接下来的操作了

小结论

run方法在整合小段代码的功效上,还是很实用的

其他高阶函数

上面列举出来的这些系统高阶函数原理上都差不多,只是使用场景有区别,因此除了run之外,其他的不再详解,而只说明使用场景。

apply

和run只有一个区别,run是返回block()执行之后的返回值,而,apply 则是返回this,因此 apply必须依赖对象。而由于返回了this,因此可以连续apply调用。

fun testApply() {

val a = A()

a.apply {

println(“如果一个对象要对它进行多个阶段的处理”)

}.apply {

println(“那么多个阶段都挤在一起则容易混淆,”)

}.apply {

println(“此时可以用apply将每一个阶段分开摆放”)

}.apply {

println(“让程序代码更加优雅”)

}

}

fun main() {

testApply()

}

with

下面的代码应该都很眼熟,Glide图片加载框架的用法,with(context)然后链式调用

Glide.with(this).load(image).asGif().into(mImageView);

Kotlin中的with貌似把这一写法发扬光大了(只是类比,不用深究),场景如下:

class A {

val a = 1

val b = “b”

fun showA() {

println(“$a”)

}

fun showB() {

println(“$a $b”)

}

}

fun testWith() {

val a = A()

with(a) {

println(“作用域中可以直接引用创建出的a对象”)

this.a

this.b

this.showA()

this

}.showB()

}

fun main() {

testWith()

}

细节

  1. with(a){} 大括号内的作用域里面,可以直接使用 当前a对象的引用,可以this.xxx 也可以 a.xxx

  2. with(a){} 大括号作用域内的最后一行是 返回值,如果我返回this,那么with结束之后,我可以继续 调用a的方法

also

also和with一样,必须依赖对象,返回值为this。因此它也支持链式调用,它和apply的区别是:

apply的block,没有参数,但是 also 则将this作为了参数。这么做造成的差异是:

作用域 { } 内调用当前对象的方式不同。

class A {

val a = 1

val b = “b”

fun showA() {

println(“$a”)

}

fun showB() {

println(“$a $b”)

}

}

fun testApply() {

A().apply {

this.showA()

println(“=======”)

}.showB()

}

fun testAlso() {

A().also {

it.showA()

println(“=======”)

}.showB()

}

apply 必须用this.xxx, also则用 it.xxx.

let

类比到run:

public inline fun <T, R> T.run(block: T.() -> R): R {

return block()

}

public inline fun <T, R> T.let(block: (T) -> R): R {

return block(this)

}

只有一个区别:run的block执行不需要参数,而let 的block执行时需要传入this。

造成差异为:

A().run {

println(“最后一行为返回值”)

this

}.showA()

A().let {

println(“最后一行为返回值”)

it

}.showA()

run{} 作用域中 只能通过this.xxx操作当前对象,let 则用 it.xxx

takeif 和 takeunless

这两个作用相反,并且他们必须依赖对象。看源码:

public inline fun T.takeIf(predicate: (T) -> Boolean): T? {

return if (predicate(this)) this else null

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!

Android架构师之路很漫长,一起共勉吧!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

转存中…(img-f9Ou9c2t-1712469753967)]

[外链图片转存中…(img-glHxY9dE-1712469753967)]

[外链图片转存中…(img-CvOqVQ2f-1712469753968)]

[外链图片转存中…(img-qUkGhpBH-1712469753968)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水!

为什么某些人会一直比你优秀,是因为他本身就很优秀还一直在持续努力变得更优秀,而你是不是还在满足于现状内心在窃喜!

Android架构师之路很漫长,一起共勉吧!

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐