scala筆記之惰性賦值(lazy)

1、lazy關鍵字簡介

lazy是scala中用來實現惰性賦值的關鍵字,被lazy修飾的變量初始化的時機是在第一次使用此變量的時候纔會賦值,而且僅在第一次調用時計算值,即值只會被計算一次,賦值一次,再以後不會被更改了,這個特性有點熟悉哎?沒錯,因此lazy修飾的變量必須同時是val修飾的不可變變量。html

 

下面是一個惰性賦值的例子:java

package cc11001100.scala.lazyStudy

class FooBar() {

  println("foo before")
  lazy val foo: String = initFoo()
  println("foo after")

  println("bar before")
  val bar: String = initBar()
  println("bar after")

  def initFoo(): String = {
    println("initFoo exec")
    "foo"
  }

  def initBar(): String = {
    println("initBar exec")
    "bar"
  }

}

object LazyStudy {
  
  def main(args: Array[String]): Unit = {

    val foobar = new FooBar()

    println(foobar.foo)
    println(foobar.foo)

    println(foobar.bar)
    println(foobar.bar)

  }

}

輸出:jvm

foo before
foo after
bar before
initBar exec
bar after
initFoo exec
foo
foo
bar
bar

須要注意的是lazy修飾的變量後面只須要是個表達式就能夠,通常是調用個方法計算值,也能夠是字面值常量,但字面值常量的話又有什麼意義呢?工具

 

2、原理探究

scala也是編譯成字節碼跑在jvm上的,而jvm的字節碼指令並無提供對lazy這種語義的支持,因此由此能夠推斷,lazy只是一個語法糖,scala編譯器在編譯時期對其作一些包裝轉換,但到底是如何轉換的呢,能夠寫一段代碼編譯而後反編譯看一下。ui

編寫一段scala代碼,有兩個變量,一個使用lazy修飾,一個不使用lazy修飾:this

package cc11001100.scala.lazyStudy

class LazyInitDemoForDecompilation {
  lazy val foo = "foo"
  val bar = "bar"
}

object LazyInitDemoForDecompilation {

  def main(args: Array[String]): Unit = {
    val o = new LazyInitDemoForDecompilation()
    println(o.foo)
    println(o.bar)
  }

}

而後編譯爲字節碼文件,再使用jd-gui等工具將其反編譯:scala

package cc11001100.scala.lazyStudy;

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes="\006\001}2A!\003\006\001#!)q\003\001C\0011!A1\004\001EC\002\023\005A\004C\004&\001\t\007I\021\001\017\t\r\031\002\001\025!\003\036\017\0259#\002#\001)\r\025I!\002#\001*\021\0259b\001\"\001+\021\025Yc\001\"\001-\005qa\025M_=J]&$H)Z7p\r>\024H)Z2p[BLG.\031;j_:T!a\003\007\002\0231\f'0_*uk\022L(BA\007\017\003\025\0318-\0317b\025\005y\021AC2dcE\002\004'M\0311a\r\0011C\001\001\023!\t\031R#D\001\025\025\005i\021B\001\f\025\005\031\te.\037*fM\0061A(\0338jiz\"\022!\007\t\0035\001i\021AC\001\004M>|W#A\017\021\005y\031S\"A\020\013\005\001\n\023\001\0027b]\036T\021AI\001\005U\0064\030-\003\002%?\t11\013\036:j]\036\f1AY1s\003\021\021\027M\035\021\00291\013'0_%oSR$U-\\8G_J$UmY8na&d\027\r^5p]B\021!DB\n\003\rI!\022\001K\001\005[\006Lg\016\006\002.aA\0211CL\005\003_Q\021A!\0268ji\")\021\007\003a\001e\005!\021M]4t!\r\0312'N\005\003iQ\021Q!\021:sCf\004\"AN\037\017\005]Z\004C\001\035\025\033\005I$B\001\036\021\003\031a$o\\8u}%\021A\bF\001\007!J,G-\0324\n\005\021r$B\001\037\025\001")
public class LazyInitDemoForDecompilation
{
  private String foo;
  
  private String foo$lzycompute()
  {
    // 由於在調用此方法以前已經判斷過一次標誌位的值了,
    // 因此能夠看作是一種被拆散了的DCL
    synchronized (this)
    {
      if (!this.bitmap$0)
      {
        this.foo = "foo";
        this.bitmap$0 = true;
      }
    }
    return this.foo;
  }
  
  public String foo()
  {
    // 每次獲取foo的值的時候,先判斷是否已經初始化過了,
    // 若是尚未初始化就將其初始化,不然直接將已經計算出的值返回
    return !this.bitmap$0 ? foo$lzycompute() : this.foo;
  }
  
  public String bar()
  {
    return this.bar;
  }
  
  // bar變量直接爲其賦值的
  private final String bar = "bar";
  // 這個變量是一個標誌位,用來記錄foo變量是否已經被初始化過了
  private volatile boolean bitmap$0;
  
  public static void main(String[] paramArrayOfString)
  {
    LazyInitDemoForDecompilation..MODULE$.main(paramArrayOfString);
  }
}

在object中執行:htm

package cc11001100.scala.lazyStudy;

import scala.Predef.;

public final class LazyInitDemoForDecompilation$
{
  public static  MODULE$;
  
  static
  {
    new ();
  }
  
  public void main(String[] args)
  {
    LazyInitDemoForDecompilation o = new LazyInitDemoForDecompilation();
    // 會將對變量的訪問替換成調用訪問器,
    // 這樣的話編譯器就能夠很雞賊的在訪問器方法中插入各類處理以提供N多的語法糖,挺機智的
    Predef..MODULE$.println(o.foo());
    Predef..MODULE$.println(o.bar());
  }
  
  private LazyInitDemoForDecompilation$()
  {
    MODULE$ = this;
  }
}

綜上源碼,得出結論,scala的lazy關鍵字就是編譯器在編譯期將變量的初始化過程替換爲Double Check Lock,相似於Java中的懶漢式單例模式初始化。ip

 

.編譯器

相關文章
相關標籤/搜索