lateinit
lateinit:用來修飾var
類型成員變量
乍桂,用來表示該變量可以在晚些時候初始化,用來避免不必要的空檢查。自Kotlin1.2
版本開始,lateinit也可以用來修飾頂層變量
和局部變量
榕堰。
//頂層變量
lateinit var topVariable: String
class MyTest {
//成員變量
lateinit var name: String
fun myfun() {
//局部變量
lateinit var address: String
}
}
注意:
- lateinit不能修飾
原始數(shù)據(jù)類型
,原始類型的數(shù)據(jù)應該使用默認的數(shù)據(jù)來進行初始化屈留,比如Int類型的變量可以使用0進行初始化局冰。 - lateinit修飾的
var
類型變量不能有自定義的getter
和setter
。
如果我們訪問一個 lateinit修飾的var
類型變量灌危,但是該變量沒有被初始化,那么就會拋出異常碳胳。
kotlin.UninitializedPropertyAccessException: lateinit property name has not been initialized
自Kotlin1.2
版本開始勇蝙,我們可以檢查一個lateinit var類型變量的變量是否被初始化了。
lateinit var topVariable: String
fun main() {
val myTest = MyTest()
myTest.check()
//為什么不能在這里檢車MyTest的name屬性是否被初始化了呢挨约?還不清楚
//println(myTest::name.isInitialized)
println(::topVariable.isInitialized)
}
class MyTest {
lateinit var name: String
fun check() {
if (this::name.isInitialized) {
println(name)
} else {
println("name 還沒有初始化")
}
}
}
lateinit的實現(xiàn)原理:我感覺沒什么原理吧味混,應該就是告訴編譯器不要去檢查變量是否為空。然后我們在引用變量的時候就判斷一下诫惭,如果變量為空翁锡,也就是沒有初始化,那么就拋出異常夕土,否者就正常返回馆衔。
查看反編譯的Kotlin字節(jié)碼瘟判,只看生成的getName方法
//注釋1處
@NotNull
public final String getName() {
String var10000 = this.name;
//注釋2處,檢查
if (var10000 == null) {
Intrinsics.throwUninitializedPropertyAccessException("name");
}
return var10000;
}
注釋1處角溃,標記這個方法返回不為空拷获。
注釋2處,檢查减细,如果變量為空了匆瓜,拋出異常。
總結(jié):lateinit可以用來修飾var類型變量來避免不必要的檢查未蝌,自Kotlin1.2
版本開始驮吱,我們可以檢查一個lateinit var類型變量的變量是否被初始化了。
lazy
lazy可以用來實現(xiàn)val
類型變量的延遲初始化萧吠】饭荩可以修飾成員變量,修飾頂層變量和局部變量怎憋。
//修飾頂層變量
val topInt: Int by lazy {
1
}
fun main() {
val byLazyTest = ByLazyTest()
byLazyTest.printName()
}
class ByLazyTest {
//修飾成員變量
private val name: String by lazy {
"dmw"
}
fun printName() {
//修飾局部變量
val address: String by lazy {
"dmw"
}
print(this.name)
}
}
我們以一個最簡單的例子來分析又碌,如下所示:
class LazyInitDemo {
val myName: String by lazy(getNameFromPreference())
private fun getNameFromPreference(): () -> String = { "John" }
}
其實我們也可以簡單的寫成下面這樣。
class LazyInitDemo {
val myName: String by lazy{"John"}
}
我們先來看看 lazy 這個方法的簽名
方法定義在LazyJVM.kt文件中
/**
* 使用指定的初始化函數(shù)[initializer]來創(chuàng)建一個[Lazy] 類型的對象绊袋。
* 默認的線程模型是[LazyThreadSafetyMode.SYNCHRONIZED].
*/
public actual fun <T> lazy(initializer: () -> T): Lazy<T> =
SynchronizedLazyImpl(initializer)
注釋中已經(jīng)很清晰了:使用指定的初始化函數(shù)[initializer]來創(chuàng)建一個[Lazy] 類型的對象毕匀。默認的線程模型是[LazyThreadSafetyMode.SYNCHRONIZED]。返回的是一個SynchronizedLazyImpl
類型的對象癌别。線程模型是什么東西皂岔?就是為了處理傳入的初始化函數(shù)[initializer]的調(diào)用,在單線程或者多線程下的調(diào)用展姐。lazy
方法還有一個重載函數(shù)躁垛,我們可以顯式的傳入線程模型。
public actual fun <T> lazy(mode: LazyThreadSafetyMode, initializer: () -> T): Lazy<T> =
when (mode) {
LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)
}
這里我們就不討論線程模型的問題了圾笨,大家可以看一看教馆,很簡單。
通過上面的敘述我們可以知道擂达,by lazy指定的函數(shù)就是一個lambda表達式{ "John" }
土铺,是一個() -> T
類型的初始化函數(shù)。
查看反編譯的Kotlin字節(jié)碼
public final class LazyInitDemo {
//注釋1處
@NotNull
private final Lazy myName$delegate = LazyKt.lazy(this.getNameFromPreference());
@NotNull
public final String getMyName() {
Lazy var1 = this.myName$delegate;
//注釋2處
return (String)var1.getValue();
}
private final Function0 getNameFromPreference() {
return (Function0)null.INSTANCE;
}
}
注釋1處板鬓,生成了一個Lazy
類型的變量myName$delegate
悲敷,就是我們的屬性名加上后綴$delegate
。我們知道默認情況下返回的是一個SynchronizedLazyImpl對象俭令。
注釋2處后德,調(diào)用SynchronizedLazyImpl的getValue方法。我們來看一看:
SynchronizedLazyImpl類在LazyJVM.kt文件中
//LazyJVM.kt
internal object UNINITIALIZED_VALUE
UNINITIALIZED_VALUE用來表示value是否初始化了
//LazyJVM.kt
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
//重寫了value屬性
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
//注釋1處抄腔,已經(jīng)初始化了瓢湃,直接返回
return _v1 as T
}
//使用同步方法理张,避免多次初始化
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
//注釋2處,別的線程已經(jīng)初始化了箱季,直接返回
(_v2 as T)
} else {
//注釋3處涯穷,調(diào)用傳入的lambda進行初始化并返回
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
//是否已經(jīng)初始化了
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
//...
}
注釋1處,如果 _value!= UNINITIALIZED_VALUE
藏雏,說明已經(jīng)調(diào)用傳入的initializer
初始化過了拷况,直接返回_value
。
注釋2處掘殴,如果別的線程已經(jīng)初始化了赚瘦,直接返回_value
。
注釋3處奏寨,當前還沒有初始化就調(diào)用initializer
進行初始化起意,并將初始化值賦值給_value
,然后返回病瞳。
以后每次調(diào)用getValue方法都是直接將_value
的值返回揽咕,從而實現(xiàn)屬性值的唯一一次初始化。
其實到這里lazy的實現(xiàn)原理已經(jīng)很清楚了套菜,我們再結(jié)合圖片總結(jié)一下:
- 將初始化lambda表達式存儲在initializer中
- SynchronizedLazyImpl重寫了Lazy的value的
getter
方法亲善, 調(diào)用getValue方法返回_value
。_value
的默認值是UNINITIALIZED_VALUE
逗柴,表示還沒有進行初始化蛹头。
- 如果調(diào)用getValue方法的時候,
_value
值是UNINITIALIZED_VALUE
戏溺,那么就調(diào)用initializer進行初始化渣蜗,并將初始化好的值賦值給_value
。
- 以后每次調(diào)用getValue方法都是直接將
_value
的值返回旷祸。
參考鏈接: