【Kotlin学习】Lambda编程——序列、使用Java函数式接口、with和apply函数

惰性集合操作:序列

map和filter函数会及早的创建中间集合,也就说每一步的中间结果都被存储在一个临时列表。序列给了你执行这些操作的另一种选择,可以避免创建这些临时中间对象。

在这里插入图片描述

filter和map都会返回一个列表,这意味着上图的链式调用会创建两个列表:一个保存filter函数的结果,一个保存map函数的结果。当元素很多时这种方式非常低效

把操作变成序列
在这里插入图片描述

上图没有创建任何用于存储元素的中间集合

kotlin惰性集合操作的入口就是Sequence接口,这个接口表示一个可以逐个列举元素的元素序列,它只提供了一个方法,iterator,用来从序列中获取值。这个接口的强大之处在于其操作的实现方式,序列中的元素求值是惰性的,用序列可以更高效地执行链式操作,而不用创建额外地集合来保存过程中产生的中间结果

可以调用扩展函数asSequence把任意集合转换成序列,调用toList来做反向的转换

为什么要把序列转换回集合?
如果只需要迭代序列中的元素,可以直接使用序列。如果要使用其他API方法,比如用下标访问元素,那么就需要把序列转换成列表

执行序列操作:中间和末端操作

序列操作分为两类:中间的和末端的。一次中间操作返回的是另一个序列,这个新序列知道如何变换原始序列中的元素。而一次末端操作返回的是一个结果,这个结果可能是集合、元素、数字,或者其他从初始集合的变换序列中获取的任意对象

在上面的图中我们可以看出map和filter函数是中间操作,而toList是末端操作
上方的例子去掉toList后,它们不会进行任何操作,因为map和filter函数的变换被延期了,他们只有在获取结果的时候才会被调用。只有末端操作会触发执行所有的延期计算

对序列来说,所有操作是按顺序应用在每一个元素上:处理完第一个元素(先映射再过滤),然后完成第二个元素的处理,以此类推。这种方法意味着如果在轮到它们之前就已经取得了结果,那部分元素根本不会发生任何变换。例子:一个map加上一个find,当find找到符合条件的元素后,后面的元素无需继续完成操作。而同样的操作应用在集合上时,那么map的结果首先被求出来,然后满足判断式的一个元素会被找出来。惰性方法意味着你可以跳过处理部分元素。

在集合上执行操作的顺序也会影响性能,filter和map之间,先应用filter有助于减少变换的总次数

创建序列

1.在集合上调用asSequence
2.generateSequence
给定序列中的前一个元素,这个函数会计算出下一个元素

生成并使用自然数序列(计算100以内所有自然数之和)

在这里插入图片描述

创建并使用父目录的序列
如果元素的父元素和它的类型相同(比如人类或者java文件),你可能会对它所有祖先组成的序列的特质感兴趣

在这里插入图片描述

上图可以查询文件是否放在隐藏目录中,通过创建一个其父目录的序列并检查每个目录的属性实现。通过提供第一个元素和获取每个后续元素的方式来实现,把any换成find,还可以得到想要的那个目录(对象)。注意!使用序列允许你找到需要的目录之后立即停止遍历父目录

使用Java函数式接口

kotlin的lambda可以无缝和JavaAPI互操作

以onClickListener为例

在这里插入图片描述

在java中使用匿名内部类的写法在kotlin中可以变成上图写法

这种方法可以工作的原因是OnClickListener接口只有一个抽象方法。这种接口被称为函数式接口,或者SAM接口,SAM代表单抽象方法。Java API中随处可见像Runnable和Callable这样的函数式接口,以及支持它们的方法。kotlin允许你在调用接受函数式接口作为参数的方法时使用lambda

kotlin拥有完全的函数类型,需要接受lambda作为参数的kotlin函数应该使用函数类型而不是函数式接口类型,作为这些参数的类型

把lambda当作参数传递给Java方法

可以把lambda传给任何期望函数式接口的方法
如:在Java中,void postponeComputation(int delay,Runnable computation)这个函数在kotlin中可以调用它并把一个lambda作为实参传给它,编译器会自动把它转换成一个Runnable的实例,

postponeComputation(1000){println(42)}

注意!当我们说一个Runnable的实例时,指的是一个实现了Runnable接口的匿名类的实例。编译器会帮你创建并使用lambda作为单抽象方法,在这里是run方法的方法体

通过显式创建一个实现了Runnable的匿名对象也能达到同样的效果

postponeComputation(1000,object:Runnable{
	override fun run(){
		println(1)
}
})

但是当你显式声明对象时,每次调用都会创建一个新的实例。使用lambda不一样,如果lambda没有访问任何来自定义它的函数的变量,相应的匿名类实例可以在多次调用之间重用

如果lambda从包围它的作用域中捕捉了变量,每次调用就不能重用同一个实例了,这种情况下每次调用时编译器都要创建一个新对象,其中存储着被捕捉的变量的值

lambda表达式在kotlin1.0中会被编译成一个匿名类,除非它是一个内联lambda。如果它捕捉了变量,每个被捕捉的变量会在匿名类中有对应的字段,而且每次对lambda的调用都会创建一个这个匿名类的新实例。但是对集合使用扩展方法的方式并不适用,内联函数也是。

SAM构造方法:显式地把lambda转换成函数式接口

SAM构造方法时编译器生成的函数,让你执行从lambda到函数式接口实例的显式转换,可以在编译器不会自动应用转换的上下文中使用它。比如有一个方法返回的是一个函数式接口的实例,不能直接返回一个lambda,要用SAM方法包装起来。

使用SAM构造方法来返回值

在这里插入图片描述

SAM构造方法的名称和底层函数式接口的名称一样,它只接收一个参数——一个被用作函数式接口单抽象方法体的lambda,并返回实现了这个接口的类的一个实例

除了返回值外,SAM构造方法还可以用在需要把lambda生成的函数式接口实例存储在一个变量中的情况

使用SAM构造方法来重用listener实例

在这里插入图片描述

Lambda和添加/移除监听器
lambda内部没有匿名对象那样的this,没有办法引用到lambda转换成的匿名类实例。从编译器角度看,lambda是一个代码块,不是一个对象,也不能把它当成对象引用,lambda中的this引用指向包围它的类。如果你的事件监听器在处理事件时还需要取消它自己,不能使用lambda这样做,这种情况使用实现了接口的匿名对象,在匿名对象内,this关键字指向该对象实例,可以把它传给移除监听器的API。

尽管方法调用中的SAM转换一般都自动发生,但是当把lambda作为参数传给一个重载方法时,也有编译器不能选择正确的重载的情况,此时使用显式SAM构造方法是一个好方法。

带接收者的lambda:with和apply

with函数

很多语言都有对同一个对象执行多次操作而不需要反复把对象的名称写出来的语句,在kotlin中我们使用with函数实现

使用with函数构建字母表

在这里插入图片描述

with实际上是一个接受两个参数的函数,这里的两个参数分别是stringBuilder和一个lambda,这里利用了lambda若在参数列表的最后一位可以放在括号外的约定。with函数把它的第一个参数转换成作为第二个参数传给它的lambda的接收者,可以显式地通过this引用来访问这个接收者,也可以省略。

带接收者的lambda和扩展函数
一个扩展函数某种意义上来说就是带接收者的函数。
lambda是一种类似普通函数的定义行为的方式,而带接收者的lambda是类似扩展函数的定义行为的方式

方法名称冲突
如果你当作参数传给with的对象已经有这样的方法,该方法的名称和你正在使用with的类中的方法一样。此时可以给this引用加上显式的标签来表明你要调用的是哪个方法。比如函数alphabet是类OuterClass的一个方法,如果你想引用的是定义在外部类的toString方法而不是StringBuilder,可以使用this@OuterClass.toString()

with返回的值是执行lambda代码的结果,该结果就是lambda中最后一个表达式的值,但有时候我们想返回的是接收者对象,此时依靠apply函数

apply函数

它和with函数几乎一样,唯一的区别是apply始终会返回作为实参传递给它的对象,也就是接收者对象

重写alphabet方法

在这里插入图片描述

它被声明成一个扩展函数,它的接收者变成了作为实参的lambda的接收者。执行apply的结果是一个StringBuilder,我们可以用toString把它转换成String。

许多情况下apply都很有效,其中一种是在创建一个对象实例并需要用正确的方式初始化它的一些属性的时候。在java中通常通过另一个单独的Builder对象来完成,而在kotlin中,可以在任意对象上使用apply,完全不需要任何来自定义该对象的库的支持

使用apply初始化一个TextView

在这里插入图片描述

TextView省略了变量的命名,在alphabet中我们可以看到变量stringBuilder是可以省略的,TextView实例被创建后立即传给了apply,在传给apply的lambda中,TextView实例变成了lambda的接收者,然后我们就可以调用它的方法设置它的属性。lambda之性质或返回已经初始化过的接收者实例,变成了createTextView的返回结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值