scala笔记之惰性赋值(lazy)
一、lazy关键字简介
lazy是scala中用来实现惰性赋值的关键字,被lazy修饰的变量初始化的时机是在第一次使用此变量的时候才会赋值,并且仅在第一次调用时计算值,即值只会被计算一次,赋值一次,再之后不会被更改了,这个特性有点熟悉哎?没错,所以lazy修饰的变量必须同时是val修饰的不可变变量。
下面是一个惰性赋值的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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) } } |
输出:
1 2 3 4 5 6 7 8 9 10 | 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修饰:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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等工具将其反编译:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | 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中执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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中的懒汉式单例模式初始化。
.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架