鍍金池/ 教程/ Scala/ 延遲初始化(Lazy vals)
 初始化抽象 vals
延遲初始化(Lazy vals)
Type 成員
抽象類型
預(yù)先初始化成員的值

延遲初始化(Lazy vals)

除了前面介紹的預(yù)先初始化成員值外,你還是讓系統(tǒng)自行決定何時初始化成員的初始值,這是通過在 val 定義前面添加 lazy(懶惰),也是說直到你第一次需要引用該成員是,系統(tǒng)才會去初始化,否則該成員就不初始化(這也是 lazy 的由來:-)). 首先我們定義一個正常定義 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 對象時,它的成員 x 也會初始化,初始化 x 伴隨著初始化 Demo 的過程。然后,如果我們在 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 時,不會初始化 x,只有在引用到 Demo.x 該初始化代碼才會執(zhí)行。 這有點類似定義了一個無參數(shù)的方法,但和 def 不同的是,lazy 變量初始化代碼只會執(zhí)行一次。 通過這個例子,我們可以看到例如 Demo 的對象本身也像一個 lazy 變量,也是在第一次引用時才會初始化,這是正確的,實際上一個 object 定義可以看成是使用了lazy val定義一個匿名類實例的簡化方式。

使用l azy val,我們可以修改之前的 RationalTrait, 在這個新的 Trait 定義中,所有的類成員變量的實現(xiàn)(非抽象成員)都使用 lazy 來修飾。


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
}

同時我們把 require 移動到 g 里面,這樣所有的 lazy val 初始化代碼都移動到 val 定義的右邊。我們不再需要預(yù)先初始化成員變量。測試如下:


scala> val x = 2
x: Int = 2

scala> new LazyRationalTrait{
    val numerArg = x
    val denomArg = 2 * x
}

res2: LazyRationalTrait = 1/2

我們來分析一下這段代碼中命令行的執(zhí)行順序:

  1. 首先,創(chuàng)建了一個新的 LazyRationalTrait 的實例,執(zhí)行 LazyRationalTrait 的初始化代碼,這部分代碼為空,LazyRationalTrait 所有成員變量都沒有初始化。
  2. 其次,該 Trait 的匿名子類的主構(gòu)造函數(shù)被執(zhí)行,這部分初始化 numberArg 和 denomArg 為2和4.
  3. 接下來,命令行需要調(diào)用該實例的 toString 方法來顯示該實例的值。
  4. 接下來,toString 需要訪問成員 number 這是第一次訪問該成員,因此 lazy val 初始化代碼被執(zhí)行。初始化代碼調(diào)用私有成員g,因此需要計算 g 的值,用到之前定義過的 numberArg 和 denomArg。
  5. 接下來 toString 需要訪問成員 denom 這是第一次訪問該成員,因此 lazy val 初始化代碼被執(zhí)行。初始化代碼調(diào)用私有成員 g ,因此需要計算 g 的值,因為 g 已經(jīng)計算過,無需再計算。
  6. 最后,toString 的結(jié)果1/2構(gòu)造出來并顯示。

在這個例子中,我們在寫代碼時,g 定義在 number 和 denom 的后面,然而,由于這三個變量都是使用 lazy 來定義的,因此它們在代碼中出現(xiàn)的順序并不重要。

上一篇:Type 成員下一篇: 初始化抽象 vals