除了前面介紹的預(yù)先初始化成員值外,你還是讓系統(tǒng)自行決定何時(shí)初始化成員的初始值,這是通過(guò)在 val 定義前面添加 lazy(懶惰),也是說(shuō)直到你第一次需要引用該成員是,系統(tǒng)才會(huì)去初始化,否則該成員就不初始化(這也是 lazy 的由來(lái):-)). 首先我們定義一個(gè)正常定義 val 的例子:
object Demo {
val x = { println("initializing x"); "done"}
}
我們首先引用 Demo,然后 Demo.x
scala> Demo
initializing x
res0: Demo.type = Demo$@78178c35
scala> Demo.x
res1: String = done
正如你所看到的,當(dāng)引用 Demo 對(duì)象時(shí),它的成員 x 也會(huì)初始化,初始化 x 伴隨著初始化 Demo 的過(guò)程。然后,如果我們?cè)?val x 前添加 lazy ,情況就有所不同了:
object Demo {
lazy val x = { println("initializing x"); "done"}
}
defined object Demo
scala> Demo
res0: Demo.type = Demo$@7de1c412
scala> Demo.x
initializing x
res1: String = done
在使用 lazy 之后,初始化 Demo 時(shí),不會(huì)初始化 x,只有在引用到 Demo.x 該初始化代碼才會(huì)執(zhí)行。 這有點(diǎn)類似定義了一個(gè)無(wú)參數(shù)的方法,但和 def 不同的是,lazy 變量初始化代碼只會(huì)執(zhí)行一次。 通過(guò)這個(gè)例子,我們可以看到例如 Demo 的對(duì)象本身也像一個(gè) lazy 變量,也是在第一次引用時(shí)才會(huì)初始化,這是正確的,實(shí)際上一個(gè) object 定義可以看成是使用了lazy val定義一個(gè)匿名類實(shí)例的簡(jiǎn)化方式。
使用l azy val,我們可以修改之前的 RationalTrait, 在這個(gè)新的 Trait 定義中,所有的類成員變量的實(shí)現(xiàn)(非抽象成員)都使用 lazy 來(lái)修飾。
trait LazyRationalTrait{
val numerArg :Int
val denomArg :Int
lazy val numer = numerArg/g
lazy val denom = denomArg/g
private lazy val g = {
require(denomArg !=0)
gcd(numerArg,denomArg)
}
private def gcd(a:Int,b:Int):Int =
if(b==0) a else gcd(b, a % b)
override def toString = numer + "/" + denom
}
同時(shí)我們把 require 移動(dòng)到 g 里面,這樣所有的 lazy val 初始化代碼都移動(dòng)到 val 定義的右邊。我們不再需要預(yù)先初始化成員變量。測(cè)試如下:
scala> val x = 2
x: Int = 2
scala> new LazyRationalTrait{
val numerArg = x
val denomArg = 2 * x
}
res2: LazyRationalTrait = 1/2
我們來(lái)分析一下這段代碼中命令行的執(zhí)行順序:
在這個(gè)例子中,我們?cè)趯懘a時(shí),g 定義在 number 和 denom 的后面,然而,由于這三個(gè)變量都是使用 lazy 來(lái)定義的,因此它們?cè)诖a中出現(xiàn)的順序并不重要。