一、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=" 06 01}2A! 03 06 01#!)q 03 01C 011!A1 04 01EC 02 23 05A 04C 04&