在Kotlin当中,我们经常会用到一些扩展函数,比如let,可以用来判空之后的操作,还有apply可以用来做glide或者okhttp这种建造者模式,直接写属性设置就行,不需要重复写变量名。
那么这些方法是怎么实现的呢,我们先来看看源码
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
是不是蒙了,这是什么玩意儿啊,写的每个单词都认识,连一起咋呀看不懂。
我看这个函数看了很多边,解构过好几次,绕来绕去,最后总是把自己绕晕,咋也理解不了。
最后我想了想,尝试自己去实现一个let函数,等实现了,也算是搞懂了这几个函数的逻辑,下面来一步步把我的实现思路写一下
首先,让我们先写一个简单的扩展函数
fun Int.test(a:String):String{
return a
}
这就是给Int加了一个名字为test的扩展函数,参数和返回值都为String类型。
然后,我们把参数a改一下,改成一个参数为Int返回值是String的函数
fun Int.test(a:(b:Int)->String):String{
return a(this)
}
因为a方法的返回值是String类型,所以test方法的返回值可以直接使用a方法的返回值,a的参数是Int,可以直接用当前Int对象的值,也就是a(this)。
这时候我们把Int类型和String类型,替换成泛型
fun <T,R>T.test(a:(b:T)->R):R{
return a(this)
}
//对比两个方法,是不是就一致了。
//inline的功能大家自行了解,不影响逻辑
public inline fun <T, R> T.let(block: (T) -> R): R {
//这个方法类似断言,对逻辑本身没有影响
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
如果要调用我们可以这么写
//先定义一个参数为Int,返回类型为String的方法
fun c(it:Int):String{
return it+1
}
//调用
1.test(::c)
//这时候变成lambda函数
1.test{
//it是lambda自己提供的默认变量
"变量经过处理之后:$it"
}
调用可以这么理解,一个叫test的方法有两个参数,一个是要操作的对象,也就是1,一个是要怎么操作,也就是中括号内的内容(可以当成是匿名函数)。
这时候,把with的方法拿出来
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
是不是就跟let是一个东西了,上面的变量1对应了receiver参数,只不过写法不一样。当然这些东西本来也就是语法糖,本质上就是一个静态方法。写起来方便就是最大的优势。比如可以这么写
1.test { }.test { }.test { }.test { }
可以无限级联,with是做不到的,这种无限级联的是不是就会让我们联想到list的操作函数,filter,map,forEach等方法。逻辑其实都是类似的,只不过理解起来有点绕。
当然还有一个类型,apply
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
老规矩,从简单的来
fun Int.test2(a:Unit):Int{
return this
}
替换a为一个没有返回值和入参的方法
fun Int.test2(a:()->Unit):Int{
//传接进来的方法需要调用
a()
return this
}
//这时候如果我们调用,可以这么写
fun c(){ }
test2(::c)
这时候我们会发现方法c里面没有办法调用到Int对象本身的方法,咋办,把c变成Int的扩展方法
fun Int.c(){}
//调用
test2(Int.c())
//那么对应的,test2内的参数需要限定为Int的方法
fun Int.test2(a:Int.()->Unit):Int{
a()
return this
}
替换泛型
fun <T> T.test2(a:T.()->Unit):T{
a()
return this
}
//调用
val str= "abc"
str.test2 { substring(0,1)
split("a")//注意这里所有的调用都是对"abc"的操作,因为字符串的唯一性,所有的操作的结果在
//这里无意义,返回的还是"abc"本身。所以对字符串的操作大家还是别用这个函数了。
}
//对比,返回值其实是可有可无的。
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
//如果返回值与本身不同,就变成了了run方法
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
apply函数可以理解成test2有两个参数,一个是对象本身,一个是一个对象的扩展函数。
总结--apply和let最大的区别就是:
let会把对象自身当成参数传给了后面的方法,所以在lambda变形的情况下,调用要用到it代表对象本身。
apply 是限定了传入的方法必须为对象存在的方法,所以可以直接用this调用,当然this可以省略。
怎么感觉还是好绕口。。。算了,大家自己写写,慢慢理解吧!!