• scala笔记之惰性赋值(lazy)


    一、lazy关键字简介

    lazy是scala中用来实现惰性赋值的关键字,被lazy修饰的变量初始化的时机是在第一次使用此变量的时候才会赋值,并且仅在第一次调用时计算值,即值只会被计算一次,赋值一次,再之后不会被更改了,这个特性有点熟悉哎?没错,所以lazy修饰的变量必须同时是val修饰的不可变变量。

    下面是一个惰性赋值的例子:

    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)
    
      }
    
    }
    

    输出:

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

    需要注意的是lazy修饰的变量后面只需要是个表达式就可以,一般是调用个方法计算值,也可以是字面值常量,但字面值常量的话又有什么意义呢?

    二、原理探究

    scala也是编译成字节码跑在jvm上的,而jvm的字节码指令并没有提供对lazy这种语义的支持,所以由此可以推断,lazy只是一个语法糖,scala编译器在编译时期对其做一些包装转换,但究竟是如何转换的呢,可以写一段代码编译然后反编译看一下。

    编写一段scala代码,有两个变量,一个使用lazy修饰,一个不使用lazy修饰:

    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等工具将其反编译:

    package cc11001100.scala.lazyStudy;
    
    import scala.reflect.ScalaSignature;
    
    @ScalaSignature(bytes="0601}2A!030601#!)q0301C011!A10401EC022305A04C04&01	07I210117	
    31020125!033617259#02#01)
    25I!02#01*21259b01"01+2125Yc01"01-05qa25M_=J]&$H)Z7p
    >24H)Z2p[BLG.31;j_:T!a030702231f'0_*uk22L(BA07170325318-317b2505y21AC2dcE0204'M311a
    011C010123!	31R#D01252505i21B01f250531	e.37*fM061A(338jiz"22!07	03501i21AC0104M>|W#A172105y31S"A20130501
    2301027b]36T21AI0105U06430-0302%?	111336:j]36f1AY1s03212127M3521029113'0_%oSR$U-\8G_J$UmY8na&d27
    ^5p]B21!DB
    03
    I!2201K0105[06Lg160602.aA211CL0503_Q21A!268ji")210703a01e05!21M]4t!
    312'N0503iQ21Q!21:sCf04"AN371705]Z04C0135253305I$B0136210331a$o\8u}%21AF0107!J,G-324
    0521r$B01372501")
    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中执行:

    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中的懒汉式单例模式初始化。

    .

  • 相关阅读:
    初识PL/SQL
    PL/SQL基本语法
    Oracle命令备忘
    工厂模式之二 工厂方法(Factory Method)
    XMLHttpRequest 原始AJAX初步
    DOM元素的innerHTML属性
    如果用JavaScript获取标准下拉框的"选中值"和"选中文本"
    工厂模式之三 抽象工厂(Abstract Factory)模式
    JavaScript中的动态参数
    JavaScript中的闭包初探
  • 原文地址:https://www.cnblogs.com/cc11001100/p/10243616.html
Copyright © 2020-2023  润新知