对比学习:
https://github.com/MindorksOpenSource/from-java-to-kotlin
1、lateinit vs lazy
lateinit :延迟初始化属性与变量
class Test {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test fun test() {
subject.method() // 直接解引用
}
}
该修饰符只能用于在类体中的属性(不是在主构造函数中声明的 var
属性,并且仅当该属性没有自定义 getter 或 setter 时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。
lazy:惰性初始化
class Test {
val subject: TestSubject by lazy { TestSubject() }
}
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
lazy()是一个接收lambda表达式,并返回Lazy<T> 实例的函数。返回的实例可以作为实现延迟属性的委托,查看SynchronizedLazyImpl源码, 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
如何选择?
- lazy只能用于val属性,而lateinit只能应用于vars,因为它不能编译为最终字段,因此不能保证不变性。
- lateinit变量可以从任何可以看到对象的地方初始化。如果您希望您的属性以一种可能事先未知的方式从外部初始化,请使用lateinit。
2、内联扩展函数apply
- 一般结构
object.apply{
//todo
}
- 定义
public inline fun <T> T.apply(block: T.() -> Unit): T
- 功能
apply接受一个函数,并将其作用域设置为调用apply的对象的作用域。这意味着不需要显式引用对象。
apply不仅仅是简单地设置属性。它是一个转换函数,能够在返回之前评估复杂的逻辑。最后,函数只返回相同的对象(添加了更改),因此可以在同一行代码中继续使用它。
- 适用场景
他和run函数的区别,run返回的是最后一行,apply返回的是对象本身,由apply函数的定义我们可以看出apply适用于那些对象初始化需要给其属性赋值的情况。
由于apply函数返回的是其对象本身,那么可以配合?.完成多级的非空判断操作,或者用于建造者模式的Builder中
val user = User().apply {
name = "Amit Shekhar"
age = 22
phoneNum = "1552156245"
}
val result = user.apply {
println("my name is $name, I am $age years old, my phone number is $phoneNum")
}
println("result: $result")
3、内联函数with
- 一般结构
with(object){
//todo
}
- 定义
public inline fun <T, R> with(receiver: T, block: T.() -> R): R
- 功能
将对象作为函数的参数,在函数内可以通过 this指代该对象。返回值为函数的最后一行或return表达式。
var user=User()
//初始化数据
with(user) {
name = "Amit Shekhar"
age = 22
phoneNum = "1552156245"
}
//返回其他数据
var copyUser = with(user) {
User(name!!, age!!, phoneNum!!)
}
4、内联扩展函数let
- 一般结构
object.let{
it.todo()//在函数体内使用it替代object对象去访问其公有的属性和方法
...
}
//另一种用途 判断object为null的操作
object?.let{//表示object不为null的条件下,才会去执行let函数体
it.todo()
}
- 定义
public inline fun <T, R> T.let(block: (T) -> R): R
- 适用场景
1、对一个可null的对象统一做判空处理。
2、对当前对象执行操作并返回基于用例的任何值
//注意:不必写“return@let”。这样做只是为了增强代码的可读性。
//在Kotlin中,如果“let”块中的最后一个语句是非赋值语句,则默认为return语句。
val person = User().let {
return@let "The name of the Person is: ${it.name}"
}
println(person)
val person = User().let {
it.name = "NewName"
}
print(person)
//它通过使用“It”关键字引用对象的上下文,因此,可以将此“It”重命名为可读的lambda参数。
val person = User().let { personDetails ->
personDetails.name = "NewName"
}
print(person)
//空值判断操作
val name=user.name?.let {
"The name of the Person is: $it"
}
print(name)
//对调用链的结果执行操作时,也可以使用“let”
val numbers = mutableListOf("One","Two","Three","Four","Five")
numbers.map { it.length }.filter { it > 3 }.let {
print(it)
}
output:
The name of the Person is: null
kotlin.Unit
kotlin.Unit
The name of the Person is: Amit Shekhar
[5, 4, 4]
5、内联扩展函数run
- 一般结构
object.run{
//todo
}
- 定义
public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <R> run(block: () -> R): R
- 功能
调用run函数返回值为函数体最后一行,或return表达式。
run函数实际上可以说是let和with两个函数的结合体,run函数只接收一个lambda函数为参数,以闭包形式返回,返回值为最后一行的值或者指定的return的表达式。
- 适用场景
适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理。
如果run与let在接受任何返回值方面类似,那有什么区别呢?不同之处在于run将对象的上下文称为“this”,而不是“it”。run由于上下文被称为“this”,因此不能将其重命名为可读的lambda参数。
val result=User().run {
name = "Asdf"
age = 18
phoneNum = "1532687459"
return@run "The details of the Person is:" +
"\n name:${this.name}" +
"\n age:${this.age}" +
"\n phoneNum:${this.phoneNum}"
}
println(result)
output:
The details of the Person is:
name:Asdf
age:18
phoneNumber:1532687459
6、内联扩展函数之also
- 一般结构
object.also{
//todo
}
-
定义
public inline fun <T> T.also(block: (T) -> Unit): T
- 功能
调用对象的also函数,在函数块内可以通过 it 指代该对象,返回值为该对象本身。(注意其和let函数的区别,let返回的是最后一行,also返回的是对象本身)
- 适用场景
需要返回对象本身(this)的情况下,例如建造者模式。适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。
7、函数区别
补充学习:https://blog.csdn.net/u013064109/article/details/80387322
apply vs with 区别:
-
apply接受一个实例作为接收器,而with要求将一个实例作为参数传递。在这两种情况下,实例都将成为一个块内的实例。
- apply返回接收器,with返回其块中最后一个表达式的结果。
通常在需要对对象执行操作并返回时使用apply,当需要对某个对象执行某些操作并返回可用的其他对象时使用with。
with vs run
private fun performWithOperation() {
val person: User? = null
with(person) {
this?.name = "asdf"
this?.age = 15
this?.phoneNumber = "12312365486"
this?.displayInfo()
}
}
private fun performRunOperation() {
val person: User? = null
person?.run {
name = "asdf"
age = 20
phoneNumber = "13698745896"
displayInfo()
}
}
通过user对象可以为空的情况,发现“with”运算符执行空检查有点麻烦,用run会更简便。
函数名 | 定义inline的结构 | 函数体内适用的对象 | 返回值 | 是否扩展函数 | 使用场景 |
let | fun <T, R> T.let(block: (T) -> R): R = block(this) | it 指代当前对象 | 闭包形式返回 | 是 | 适用于处理不为null的操作场景 |
with | fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block() | this 指代当前对象或者省略 | 闭包形式返回 | 否 | 适用于调用同一个类的多个方法时,可以省去类名重复,直接调用类的方法即可,经常用于Android中RecyclerView中onBinderViewHolder中,数据model的属性映射到UI上 |
run | fun <T, R> T.run(block: T.() -> R): R = block() | this 指代当前对象或者省略 | 闭包形式返回 | 是 | 适用于let,with函数任何场景。 |
apply | fun T.apply(block: T.() -> Unit): T { block(); return this } | this 指代当前对象或者省略 | 返回this | 是 | 1、适用于run函数的任何场景,一般用于初始化一个对象实例的时候,操作对象属性,并最终返回这个对象。 2、动态inflate出一个XML的View的时候需要给View绑定数据也会用到. 3、一般可用于多个扩展函数链式调用 4、数据model多层级包裹判空处理的问题 |
also | fun T.also(block: (T) -> Unit): T { block(this); return this } | it 指代当前对象 | 返回this | 是 | 适用于let函数的任何场景,一般可用于多个扩展函数链式调用 |