共通篇
Lazy变量也就是常说的惰性加载,即变量在初始化时没有进行计算操作,而是延迟到了该变量第一次被使用的时候。在函数式编程中,惰性加载被广泛使用,尤其是对于一些长度未知的列表,如果使用普通方式,恐怕加载时系统就会被拖慢速度甚至于内存溢出。
Java
Java 并不支持惰性加载,所以以往一般都是通过代理对象的形式来模拟实习。不过从 Java 1.8 开始可以使用更简单的 Lambda 表达式来实现此功能。
class A {
private Supplier<Long> fooField = () -> {
Long val = expensiveInit();
fooField = () -> val;
return val;
};
protected Long expensiveInit() {
System.out.println("Compute...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return System.currentTimeMillis();
}
public Long getFoo() {
return fooField.get();
}
}
调用以下代码观察控制台输出就可以看出区别
System.out.println("Before is " + System.currentTimeMillis());
A a = new A();
System.out.println("After is " + System.currentTimeMillis());
System.out.println("Hello 1, " + a.getFoo());
System.out.println("Hello 2, " + a.getFoo());
Groovy
Groovy 中可以通过 @Lazy
注解和自释放的闭包来实现惰性加载。
class A {
def fooField = {
println("Compute...")
Thread.sleep(3000)
System.currentTimeMillis()
}()
@Lazy
def lazyField = {
println("Compute...")
Thread.sleep(3000)
System.currentTimeMillis()
}()
}
调用以下代码观察控制台输出就可以看出区别
println("Before is ${System.currentTimeMillis()}")
def a = new A()
println("After is ${System.currentTimeMillis()}")
println("Hello 1, ${a.fooField}")
println("Hello 2, ${a.fooField}")
println("Hello 1, ${a.lazyField}")
println("Hello 2, ${a.lazyField}")
Scala
lazy
Scala 中通过 lazy
关键字来实现惰性加载的功能。
class A {
val fooField = {
println("Compute...")
Thread.sleep(3000)
System.currentTimeMillis()
}
lazy val lazyField = {
println("Compute...")
Thread.sleep(3000)
System.currentTimeMillis()
}
}
调用以下代码观察控制台输出就可以看出区别
println(s"Before is ${System.currentTimeMillis()}")
val a = new A()
println(s"After is ${System.currentTimeMillis()}")
println(s"Hello 1, ${a.fooField}")
println(s"Hello 2, ${a.fooField}")
println(s"Hello 1, ${a.lazyField}")
println(s"Hello 2, ${a.lazyField}")
var, val, lazy val, def 的区别
var
, val
, lazy val
, def
这四个在有些时候用法看起来有些相似,很容易让人搞混。接下来通过一个例子来进行区分:
例
class A {
var max = 100
def init(m: Int) = max = m
val valValue = Random.nextInt(max)
var varValue = Random.nextInt(max)
lazy val lazyValValue = Random.nextInt(max)
def defValue = Random.nextInt(max)
}
val a = new A()
a.init(1000000)
println(s"val=${a.valValue}") // 96
println(s"var=${a.varValue}") // 9
println(s"lazy val=${a.lazyValValue}") // 117261
println(s"def=${a.defValue}") // 798062
println(s"val=${a.valValue}") // 96
println(s"var=${a.varValue}") // 9
println(s"lazy val=${a.lazyValValue}") // 117261
println(s"def=${a.defValue}") // 937263
从以上的例子中可以知道这四个有以下的区别
var
定义变量,在类初始化时的同时被初始化,除非被手动赋值,再次访问时会直接使用之前的值val
定义值(常量),在类初始化时的同时被初始化,无法被修改,再次访问时会直接使用之前的值lazy val
定义惰性加载值,在第一次使用被初始化,无法被修改,再次访问时会直接使用之前的值def
定义函数,单行时看起来很像定义变量,但是访问时实际是调用函数,所以多次调用时值会重新计算
Kotlin
Kotlin 中通过 lazy()
函数来实现惰性加载功能。
class A {
val fooField: Long by lazy {
println("Compute...")
Thread.sleep(3000)
System.currentTimeMillis()
}
}
调用以下代码观察控制台输出就可以看出区别
println("Before is ${System.currentTimeMillis()}")
val a = A()
println("After is ${System.currentTimeMillis()}")
println("Hello 1, ${a.fooField}")
println("Hello 2, ${a.fooField}")
Kotlin 中使用惰性加载时还能指定线程安全的模式,默认惰性加载是同步的(LazyThreadSafetyMode.SYNCHRONIZED,只能有一个线程进行修改,结果对所有线程可见),但是也可以指定为公开的(PUBLICATION,所有线程可以同时进行计算,但是只有第一个完成计算的线程会被作为结果) , 或者不加任何限制(NONE,没有任何线程安全的措施,效率最高,适合本来就是单线程执行计算的程序)。
val fooField2: Long by lazy(LazyThreadSafetyMode.PUBLICATION) {
println("Compute...")
Thread.sleep(3000)
System.currentTimeMillis()
}
总结
- 除了 Java 之外,其它三种语言都默认支持惰性加载。
项目源码见 JGSK/_34_lazy