Scala中的延迟初始化(Lazy vals)

延迟初始化(Lazy vals)

除了前面介绍的预先初始化成员值外,你还是让系统自行决定何时初始化成员的初始值,这是通过在 val 定义前面添加 lazy(懒惰),也是说直到你第一次需要引用该成员是,系统才会去初始化,否则该成员就不初始化(这也是 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

正如你所看到的,当引用 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 该初始化代码才会执行。这有点类似定义了一个无参数的方法,但和 def 不同的是,lazy 变量初始化代码只会执行一次。通过这个例子,我们可以看到例如 Demo 的对象本身也像一个 lazy 变量,也是在第一次引用时才会初始化,这是正确的,实际上一个 object 定义可以看成是使用了lazy val定义一个匿名类实例的简化方式。

使用l azy val,我们可以修改之前的 RationalTrait, 在这个新的 Trait 定义中,所有的类成员变量的实现(非抽象成员)都使用 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 定义的右边。我们不再需要预先初始化成员变量。测试如下:


scala> val x = 2
x: Int = 2

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

res2: LazyRationalTrait = 1/2

我们来分析一下这段代码中命令行的执行顺序:

  1. 首先,创建了一个新的 LazyRationalTrait 的实例,执行 LazyRationalTrait 的初始化代码,这部分代码为空,LazyRationalTrait 所有成员变量都没有初始化。
  2. 其次,该 Trait 的匿名子类的主构造函数被执行,这部分初始化 numberArg 和 denomArg 为2和4.
  3. 接下来,命令行需要调用该实例的 toString 方法来显示该实例的值。
  4. 接下来,toString 需要访问成员 number 这是第一次访问该成员,因此 lazy val 初始化代码被执行。初始化代码调用私有成员g,因此需要计算 g 的值,用到之前定义过的 numberArg 和 denomArg。
  5. 接下来 toString 需要访问成员 denom 这是第一次访问该成员,因此 lazy val 初始化代码被执行。初始化代码调用私有成员 g ,因此需要计算 g 的值,因为 g 已经计算过,无需再计算。
  6. 最后,toString 的结果1/2构造出来并显示。

在这个例子中,我们在写代码时,g 定义在 number 和 denom 的后面,然而,由于这三个变量都是使用 lazy 来定义的,因此它们在代码中出现的顺序并不重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值