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