If class is not a final one, it may be extended.
There are two possibilities for values: it may be overridden and should be lazy for it, it may not be overridden and should be final.
If val is final - you may assume that all computations over it would work through class hierarchy. If val may be overriden you should declare it lazy for not becoming broken after extending. You may leave val plain and this gives no guaranties it would be extended in right way.
What use cases imply using plain values?
Example of class initialization failure without lazy values
abstract class A {
lazy val x1 : String = throw new Exception()
val x2 : String = "mom"
val x3 : String = x1 + ", " + x2
println("A: " + x3)
}
class B extends A {
override lazy val x1: String = "hello"
println("B: " + x3)
}
class C extends B {
override val x2: String = "dad"
println("C: " + x3)
}
testing it:
scala> new B
A: hello, mom
B: hello, mom
res8: B = B@7e2bd615
it works, but further sub-classing broke already existing functionality
scala> new C
A: hello, null
B: hello, null
C: hello, null
res5: C = C@52a53948
setting lazy on x2 fixes the case:
abstract class A {
lazy val x1 : String = throw new Exception()
lazy val x2 : String = "mom"
val x3 : String = x1 + ", " + x2
println("A: " + x3)
}
class B extends A {
override lazy val x1: String = "hello"
println("B: " + x3)
}
class C extends B {
override lazy val x2: String = "dad"
println("C: " + x3)
}
right initialization order:
scala> new C
A: hello, dad
B: hello, dad
C: hello, dad
res6: C = C@5e970110