在这种情况下,显然 T.run 扩展函数更好,因为我们可以在使用它之前对可空性进行检查。
2、this VS it 参数(This vs. it argument)
如果我们对比 T.run 和 T.let 两个函数也是非常的相似,唯一的区别在于它们接收的参数不一样。下面显示了两种功能的相同逻辑。
stringVariable?.run {
println(“The length of this String is $length”)
}
// Similarly.
stringVariable?.let {
println(“The length of this String is ${it.length}”)
}
如果你查看过 T.run 函数声明,你就会注意到T.run仅仅只是被当做了 block: T.() 扩展函数的调用块。因此,在其作用域内,T 可以被 this 指代。在编码过程中,在大多数情况下this是可以被省略的。因此我们上面的示例中,我们可以在println语句中直接使用 l e n g t h ∗ ∗ 而不是 ∗ ∗ length** 而不是 ** length∗∗而不是∗∗{this.lenght}. 所以我把这个称之为传递 this参数
然而对于 T.let 函数的声明,你将会注意到 T.let 是传递它自己本身到函数中block: (T)。因此这个类似于传递一个lambda表达式作为参数。它可以在函数作用域内部使用it来指代. 所以我把这个称之为传递 it参数
从上面看,似乎T.run比T.let更加优越,因为它更隐含,但是T.let函数具有一些微妙的优势,如下所示:
- 1、T.let函数提供了一种更清晰的区分方式去使用给定的变量函数/成员与外部类函数/成员。
- 2、例如当it作为函数的参数传递时,this不能被省略,并且it写起来比this更简洁,更清晰。
- 3、T.let允许更好地命名已转换的已使用变量,即可以将it转换为其他有含义名称,而 T.run则不能,内部只能用this指代或者省略。
stringVariable?.let {
nonNullString ->
println(“The non null string is $nonNullString”)
}
3、返回this VS 其他类型 (Return this vs. other type)
现在,让我们看看T.let和T.also,如果我们看看它的内部函数作用域,它们都是相同的。
stringVariable?.let {
println(“The length of this String is ${it.length}”)
}
// Exactly the same as below
stringVariable?.also {
println(“The length of this String is ${it.length}”)
}
然而,他们微妙的不同在于他们的返回值T.let返回一个不同类型的值,而T.also返回T类型本身,即这个。
这两个函数对于函数的链式调用都很有用,其中T.let让您演变操作,而T.also则让您对相同的变量执行操作。
简单的例子如下:
val original = “abc”
// Evolve the value and send to the next chain
original.let {
println(“The original String is $it”) // “abc”
it.reversed() // evolve it as parameter to send to next let
}.let {
println(“The reverse String is $it”) // “cba”
it.length // can be evolve to other type
}.let {
println(“The length of the String is $it”) // 3
}
// Wrong
// Same value is sent in the chain (printed answer is wrong)
original.also {
println(“The original String is $it”) // “abc”
it.reversed() // even if we evolve it, it is useless
}.also {
println(“The reverse String is ${it}”) // “abc”
it.length // even if we evolve it, it is useless
}.also {
println(“The length of the String is ${it}”) // “abc”
}
// Corrected for also (i.e. manipulate as original string
// Same value is sent in the chain
original.also {
println(“The original String is $it”) // “abc”
}.also {
println(“The reverse String is ${it.reversed()}”) // “cba”
}.also {
println(“The length of the String is ${it.length}”) // 3
}
T.also似乎看上去没有意义,因为我们可以很容易地将它们组合成一个功能块。仔细思考,它有一些很好的优点。
- 1、它可以对相同的对象提供非常清晰的分离过程,即创建更小的函数部分。
- 2、在使用之前,它可以非常强大的进行自我操作,从而实现整个链式代码的构建操作。
当两者结合在一起使用时,即一个自身演变,一个自我保留,它能使一些操作变得更加强大。
// Normal approach
fun makeDir(path: String): File {
val result = File(path)
result.mkdirs()
return result
}
// Improved approach
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }
回顾所有属性特征
通过回顾这3个属性特征,我们可以非常清楚函数的行为。让我来说明T.apply函数,由于我并没有以上函数中提到过它。 T.apply的三个属性如下
- 1、它是一个扩展函数
- 2、它是传递this作为参数
- 3、它是返回 this (即它自己本身)
因此,使用它,可以想象下它可以被用作:
// Normal approach
fun createInstance(args: Bundle) : MyFragment {
val fragment = MyFragment()
fragment.arguments = args
return fragment
}
// Improved approach
fun createInstance(args: Bundle)
= MyFragment().apply { arguments = args }
或者我们也可以让无链对象创建链式调用。
// Normal approach
fun createIntent(intentData: String, intentAction: String): Intent {
val intent = Intent()
intent.action = intentAction
intent.data=Uri.parse(intentData)
return intent
}
// Improved approach, chaining
fun createIntent(intentData: String, intentAction: String) =
Intent().apply { action = intentAction }
.apply { data = Uri.parse(intentData) }
函数的选用
因此,显然有了这3个属性特征,我们现在可以对功能进行相应的分类。基于此,我们可以在下面构建一个决策树,以帮助确定我们想要使用哪个函数,来选择我们需要的。
希望上面的决策树能够更清晰地说明功能,并简化你的决策,使你能够适当掌握这些功能的使用.
译者有话说
- 1、为什么我要翻译这篇博客?
我们都知道在Kotlin中Standard.Kt文件中短短不到100来行库函数源码,但是它们作用是非常强大,可以说它们是贯穿于整个Kotlin开发编码过程中。使用它们能让你的代码会更具有可读性、更优雅、更简洁。善于合理使用标准库函数,也是衡量你对Kotlin掌握程度标准之一,因为你去看一些开源Kotlin源码,随处可见的都是使用各种标准库函数。
但是这些库函数有难点在于它们的用法都非常相似,有的人甚至认为有的库函数都是多余的,其实不然,每个库函数都是有它的实际应用场景。虽然有时候你能用一种库函数也能实现相同的功能,但是也许那并不是最好的实现方式。相信很多初学者对于这些标准库函数也是傻傻分不清楚(曾经的我也是),但是这篇博客非常一点在于它提取出了这些库函数三个主要特征:是否是扩展函数、是否传递this或it做为参数(在函数内部表现就是this和it的指代)、是否需要返回调用者对象本身,基于特征就可以进行分类,分类后相应的应用场景也就一目了然。这种善于提取特征思路还是值得学习的。
- 2、关于使用标准库函数需要补充的几点。
第一点: 建议尽量不要使用多个标准库函数进行嵌套,不要为了简化而去做简化,否则整个代码可读性会大大降低,一会是it指代,一会又是this指代,估计隔一段时间后连你自己都不知道指代什么了。
第二点: 针对上面译文的let函数和run函数需要补充下,他们之所以能够返回其他类型的值,其原理在于内部block lambda表达式返回的R类型,也就是这两者函数的返回值类型取决于传入block lambda表达式返回类型,然而决定block lambda表达式返回值类型,取决于外部传入lambda表达式体内最后一行返回值
第三点: 关于T.also和T.apply函数为什么都能返回自己本身,是因为在各自Lambda表达式内部最后一行都调用return this,返回它们自己本身,这个this能被指代调用者,是因为它们都是扩展函数特性 - 3、总结
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
![](https://i-blog.csdnimg.cn/blog_migrate/c70c35c231575aae1eca32f8a217e30c.jpeg)
最后
感谢您的阅读,在文末给大家准备一个福利。本人从事Android开发已经有十余年,算是一名资深的移动开发架构师了吧。根据我的观察发现,对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。
所以在此将我十年载,从萌新小白一步步成长为Android移动开发架构师的学习笔记,从Android四大组件到手写实现一个架构设计,我都有一一的对应笔记为你讲解。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
图片转存中…(img-otLz51FG-1712822722127)]
最后,赠与大家一句诗,共勉!
不驰于空想,不骛于虚声。不忘初心,方得始终。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!