Scala中使用關(guān)鍵字lazy來(lái)定義惰性變量,實(shí)現(xiàn)延遲加載(懶加載)。
惰性變量只能是不可變變量,并且只有在調(diào)用惰性變量時(shí),才會(huì)去實(shí)例化這個(gè)變量。
在Java中,要實(shí)現(xiàn)延遲加載(懶加載),需要自己手動(dòng)實(shí)現(xiàn)。一般的做法是這樣的:
public class LazyDemo {
private String property;
public String getProperty() {
if (property == null) {//如果沒(méi)有初始化過(guò),那么進(jìn)行初始化
property = initProperty();
}
return property;
}
private String initProperty() {
return "property";
}
}
比如常用的單例模式懶漢式實(shí)現(xiàn)時(shí)就使用了上面類似的思路實(shí)現(xiàn)。
而在Scala中對(duì)延遲加載這一特性提供了語(yǔ)法級(jí)別的支持:
lazy val property = initProperty()
使用lazy關(guān)鍵字修飾變量后,只有在使用該變量時(shí),才會(huì)調(diào)用其實(shí)例化方法。也就是說(shuō)在定義property=initProperty()時(shí)并不會(huì)調(diào)用initProperty()方法,只有在后面的代碼中使用變量property時(shí)才會(huì)調(diào)用initProperty()方法。
如果不使用lazy關(guān)鍵字對(duì)變量修飾,那么變量property是立即實(shí)例化的:
object LazyOps {
def init(): String = {
println("call init()")
return ""
}
def main(args: Array[String]) {
val property = init();//沒(méi)有使用lazy修飾
println("after init()")
println(property)
}
}
上面的property沒(méi)有使用lazy關(guān)鍵字進(jìn)行修飾,所以property是立即實(shí)例化的,如果觀察程序的輸出:
call init()
after init()
可以發(fā)現(xiàn),property聲明時(shí),立即進(jìn)行實(shí)例化,調(diào)用了`init()``實(shí)例化方法
而如果使用lazy關(guān)鍵字進(jìn)行修飾:
object LazyOps {
def init(): String = {
println("call init()")
return ""
}
def main(args: Array[String]) {
lazy val property = init();//使用lazy修飾
println("after init()")
println(property)
println(property)
}
}
觀察輸出:
after init()
call init()
在聲明property時(shí),并沒(méi)有立即調(diào)用實(shí)例化方法intit(),而是在使用property時(shí),才會(huì)調(diào)用實(shí)例化方法,并且無(wú)論縮少次調(diào)用,實(shí)例化方法只會(huì)執(zhí)行一次。
與Java相比起來(lái),實(shí)現(xiàn)懶加載確實(shí)比較方便了。那么Scala是如何實(shí)現(xiàn)這個(gè)語(yǔ)法糖的呢?反編譯看下Scala生成的class:
private final String property$lzycompute$1(ObjectRef property$lzy$1, VolatileByteRef bitmap$0$1)
{
synchronized (this)//加鎖
{
if ((byte)(bitmap$0$1.elem & 0x1) == 0)//如果屬性不為null
{//那么進(jìn)行初始化
property$lzy$1.elem = init();bitmap$0$1.elem = ((byte)(bitmap$0$1.elem | 0x1));
}
return (String)property$lzy$1.elem;
}
}
Scala同樣使用了Java中常用的懶加載的方式自動(dòng)幫助我們實(shí)現(xiàn)了延遲加載,并且還加鎖避免多個(gè)線程同時(shí)調(diào)用初始化方法可能導(dǎo)致的不一致問(wèn)題。
借鑒崔鵬飛的小結(jié)
對(duì)于這樣一個(gè)表達(dá)式: lazy val t:T = expr 無(wú)論expr是什么東西,字面量也好,方法調(diào)用也好。Scala的編譯器都會(huì)把這個(gè)expr包在一個(gè)方法中,并且生成一個(gè)flag來(lái)決定只在t第一次被訪問(wèn)時(shí)才調(diào)用該方法。
本文的編寫借鑒了剝開(kāi)Scala的糖衣(5) -- Lazy