Except from the added verbosity are there any other strong reasons why one shouldn't state that every instance variable should be lazily initialized ?
Lazy initialization means that whenever an object creation seems expensive, the lazy keyword can be stick before val. This gives it the advantage to get initialized in the first use i.e. the expression inbound is not evaluated immediately but once on the first access. Example: // Scala program of Lazy val.
Scala provides a nice language feature called lazy val that defers the initialization of a variable. The lazy initialization pattern is common in Java programs. Though it seems tempting, the concrete implementation of lazy val has some subtle issues.
7 Answers. Show activity on this post. The difference between them is, that a val is executed when it is defined whereas a lazy val is executed when it is accessed the first time. In contrast to a method (defined with def ) a lazy val is executed once and then never again.
First of all: if something goes wrong in the initalization of a lazy val (like accessing an external resource that does not exist), you will only notice it the first time you access the val, whereas with a normal val you will notice as soon as the object is being constructed. You can also have cyclic dependencies in lazy vals which will lead to the class not working at all (one of the dreaded NullPointerExceptions), but you will only find out the first time you access one of the connected lazy vals.
So lazy vals make the program less deterministic, which is always a bad thing.
Second: There is a runtime overhead involved with a lazy val. A lazy val is currently implemented by a private bitmask (int) in a class using lazy vals (one bit for each lazy val, so if you have more than 32 lazy vals there will be two bitmasks etc.)
To make sure that the lazy val initializer will only be run exactly once, there is a synchronized write to the bitmask when the field is initialized and a volatile read every time the field is accessed. Now a volatile read is pretty cheap on the x86 architecture, but a volatile write can be really expensive.
As far as I know there is an effort underway to optimize this in a future version of scala, but there will always be an overhead to check if the field is initialized compared to a straight val access. For example the extra code for lazy val access might prevent a method from being inlined.
Of course for a very small class the memory overhead of the bitmask might also be relevant.
But even if you don't have any performance problems, it is good to figure out the order in which vals depend on each other and just sort them in that order and use normal vals.
Edit: here is a code example that illustrates the nondeterminism you might get if you use lazy vals:
class Test {
lazy val x:Int = y
lazy val y:Int = x
}
You can create an instance of this class without any problems, but as soon as you access either x or y you will get a StackOverflow. This is of course an artificial example. In the real world you have much longer and non-obvious dependency cycles.
Here is a scala console session using :javap that illustrates the runtime overhead of a lazy val. First a normal val:
scala> class Test { val x = 0 }
defined class Test
scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public int x();
Code:
0: aload_0
1: getfield #11; //Field x:I
4: ireturn
public Test();
Code:
0: aload_0
1: invokespecial #17; //Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #11; //Field x:I
9: return
}
And now the lazy val:
scala> :javap -c Test
Compiled from "<console>"
public class Test extends java.lang.Object implements scala.ScalaObject{
public volatile int bitmap$0;
public int x();
Code:
0: aload_0
1: getfield #12; //Field bitmap$0:I
4: iconst_1
5: iand
6: iconst_0
7: if_icmpne 45
10: aload_0
11: dup
12: astore_1
13: monitorenter
14: aload_0
15: getfield #12; //Field bitmap$0:I
18: iconst_1
19: iand
20: iconst_0
21: if_icmpne 39
24: aload_0
25: iconst_0
26: putfield #14; //Field x:I
29: aload_0
30: aload_0
31: getfield #12; //Field bitmap$0:I
34: iconst_1
35: ior
36: putfield #12; //Field bitmap$0:I
39: getstatic #20; //Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
42: pop
43: aload_1
44: monitorexit
45: aload_0
46: getfield #14; //Field x:I
49: ireturn
50: aload_1
51: monitorexit
52: athrow
Exception table:
from to target type
14 45 50 any
public Test();
Code:
0: aload_0
1: invokespecial #26; //Method java/lang/Object."<init>":()V
4: return
}
As you can see, the normal val accessor is very short and will definitely be inlined, whereas the lazy val accessor is quite complex and (most importantly for concurrency) involves a synchronized block (the monitorenter/monitorexit instructions). You can also see the extra field that is generated by the compiler.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With