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函數的任何場景,一般可用於多個擴展函數鏈式調用

 

 

 

 

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