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