前言
本博客为本人学习笔记,如有不对的地方,劳请在评论区指出,望海涵
前提
Kotlin,支持闭包(block),如果函数中最后一个参数为闭包,那么最后一个参可以不写在括号中,而写在括号后面,如果只有一个参数,括号也可以去掉。(这个概念比较重要后边的 源码理解都会用到)
希望通过两个函数对比可以加深我们的学习印象
@kotlin.internal.InlineOnly
public inline fun with(receiver: T, block: T.() -> R): R = receiver.block()
with函数 接收一个 T 类型的对象和一个被作为扩展函数的函数。这个方法主要是让这个t对象去执行body函数。因为第二个参数是一个函数,所以第二个函数可以放在圆括号外边。我们可以在第二个参数里面创建代码块,在这个代码块里可以使用 this和直接访问public的方法和属性,返回最后一行结果。
@kotlin.internal.InlineOnly
public inline fun T.run(block: T.() -> R): R = block()
run函数也是一个扩展函数。 和apply函数很像,只不过run函数是使用最后一行的返回,apply返回当前自己的对象this。(apply函数我们最后讲)
我们通过 java 和 kotlin 两种方式进行对比
// java
TextView text = findViewById(R.id.tv_text)
text.("ymc")
text.setTextSize(23)
以上是java 代码,我们再通过kotlin代码分别写出 with 和 run 函数的适用方式
// kotlin
// with 函数
with(R.id.tv_text) {
tv_text.text = "ymc"
tv_text.textSize = 23f
}
// run 函数
R.id.tv_text.run {
tv_text.text = "ymc"
tv_text.textSize = 23f
}
kotlin code
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
int var2 = -1100088;
((TextView)this._$_findCachedViewById(id.tv_text)).setText((CharSequence)"ymc");
((TextView)this._$_findCachedViewById(id.tv_text)).setTextSize(23.0F);
var2 = -1100088;
((TextView)this._$_findCachedViewById(id.tv_text)).setText((CharSequence)"ymc");
((TextView)this._$_findCachedViewById(id.tv_text)).setTextSize(23.0F);
}
上面两段代码实现的功能是一样的,转换后的kotlin code 也都一样,相比与 java ,kotlin的代码更加简洁。
with / run 函数中的参数是一个对象,我们可以带方法中直接引用对象的公有属性或者公有方法,而不用使用方法名。
不同:
我们这里 举一个 WebSetting 可能为空 的例子看下两个函数的表现:
// with 函数
with(webview.settings) {
this?.javaScriptEnabled = true
this?.databaseEnabled = true
}
}
// run 函数
webview.settings?.run {
javaScriptEnabled = true
databaseEnabled = true
}
在这种情况下,显然 run 扩展函数更好,因为我们可以在使用它之前对可空性进行检查。
如果一段代码你需要多次使用一个对象,那么你就可以使用 with / run函数。
@kotlin.internal.InlineOnly
public inline fun T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
let函数 是一个扩展函数,默认当前这个对象作为闭包的it参数,返回值是函数里面最后一行,或者指定return
如果我们对比 T.run 和 T.let 两个函数也是非常的相似,唯一的区别在于它们接收的参数不一样。下面显示了两种功能的相同逻辑。
// run 函数
string?.run {
println("The length of this String is $length")
}
// let 函数
string?.let {
println("The length of this String is ${it.length}")
}
kotlin code
String string = "ymc";
String var4 = "The length of this String is " + string.length();
System.out.println(var4);
var4 = "The length of this String is " + string.length();
System.out.println(var4);
如果你查看过 T.run 函数声明,你就会注意到T.run仅仅只是被当做了 block: T.() 扩展函数的调用块。因此,在其作用域内,T 可以被 this 指代。在编码过程中,在大多数情况下this是可以被省略的。因此我们上面的示例中,我们可以在println语句中直接使用 $length 而不是 ${this.lenght}. 所以我把这个称之为传递 this参数
T.let 函数的声明,你将会注意到 T.let 是传递它自己本身到函数中block: (T)。因此这个类似于传递一个lambda表达式作为参数。它可以在函数作用域内部使用it来指代. 所以我把这个称之为传递 it参数
fun T.also(block: (T) -> Unit): T
also函数 为扩展函数,默认当前这个对象作为闭包的it参数,返回传入的参数自己本身。
我们接下来比较下 let 和 also 的区别和相同点,
// let 函数
string?.let {
println("The length of this String is ${it.length}")
}
// also 函数
string?.also {
println("The length of this String is ${it.length}")
}
然而,他们微妙的不同在于他们的返回值let返回一个不同类型的值,而also返回T类型本身,即这个。
这两个函数对于函数的链式调用都很有用,其中let让您演变操作,而also则让您对相同的变量执行操作。
我们通过一个方法能更好的理解
// 平常的使用
fun makeDir(path: String): File {
val result = File(path)
result.mkdirs()
return result
}
// 组合使用
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
从上述代码中我们可以看到 let 函数传入String ,但是返回了File对象(默认返回最后一行的表达式结果)also 则是返回File也就是 it 自身
fun T.apply(f: T.() -> Unit): T
apply 函数 也是一个扩展函数,在函数范围内,可以任意调用该对象的任意方法,并返回该对象
我们通过一个例子来看下 apply 的使用方法
//正常的
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data=Uri.parse(intentData)
return intent
}
// apply 函数
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }