lateinit 、lazy、apply 、 with

对比学习:

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函数的任何场景,一般可用于多个扩展函数链式调用

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章