Scala 中的 lazy Val

Suraj P 2023年1月30日 2022年5月18日
  1. 在 Scala 中使用 lazy val
  2. lazy val 的无限数据结构
  3. 在 Scala 中使用 lazy val 的优点
  4. 在 Scala 中使用 lazy val 的缺点
Scala 中的 lazy Val

本文将讨论 lazy val 如何在 Scala 中工作。我们还将讨论它的一些缺点。

在 Scala 中使用 lazy val

lazy val 是一种延迟变量初始化的功能,这是 Java 程序中使用的典型模式。它也被称为按需调用评估策略,其中语句直到第一次使用才被评估,这意味着将评估推迟或推迟到需要时。

我们只需在 val 之前添加关键字 lazy 即可使用它。让我们用一个例子来看看它是如何工作的。

以下代码语句在 scala REPL 中执行。

没有 lazy

scala> val marks = List(20,30,40,50)
val marks: List[Int] = List(20, 30, 40, 50)

scala> val doubleMarks = marks.map(temp=>temp*2)    //when we hit enter after statement , we get below result: the doubleMarks list is calculated
val doubleMarks: List[Int] = List(40, 60, 80, 100)


scala> println(doubleMarks)
List(40, 60, 80, 100)

使用 lazy

scala> val marks = List(50,60,70,25)
val marks: List[Int] = List(50, 60, 70, 25)

scala> lazy val doubleMarks = marks.map(temp=>temp*2)
lazy val doubleMarks: List[Int] // unevaluated

scala> println(doubleMarks)
List(100, 120, 140, 50)

Scala 编译器不会立即计算 lazy val 表达式。仅在首次访问时对其进行评估。

当初始访问完成时,编译器评估表达式,然后存储(缓存)lazy val 结果。稍后我们访问它时不会执行任何操作,并且编译器会返回存储的结果。

让我们看一个关于惰性验证结果的缓存/存储的示例:

scala> lazy val statement={ println("first time access and storing it"); 1000 }
lazy val statement: Int // unevaluated

scala> println(statement)
first time access and storing it
1000

scala> println(statement)
1000

当我们第一次打印/访问该语句时,我们会得到 1000 和一个打印语句,但是当第二次访问它时,我们只会得到 1000,因为它是在变量语句中分配和存储/缓存的值。

lazy val 的无限数据结构

lazy val 可以访问无限数据结构。

在 Scala 中,我们知道列表序列。这些列表也是严格序列意味着列表元素是预先构建的,但我们也可以创建非严格序列,其中元素是根据要求制作的。

示例代码:

object workspace {
    def main(args: Array[String]): Unit = {
        var temp = 5

        def myfun() = {
            temp += 1;
            temp
        }

        lazy val geek = Stream.continually( myfun() )

        (geek take 5) foreach {
            x => println(2*x)
        }
    }
}

输出:

12
14
16
18
20

myfun() 方法被转换为一个函数。Stream.continually() 方法创建一个无限流。

它接受该函数,并且每当访问每个元素时,仅在那时调用 myfun() 函数来执行计算。

这种按需执行的函数称为 Thunk,一旦计算完成,就会存储在缓存中以供进一步使用,这也称为记忆化。

在 Scala 中使用 lazy val 的优点

lazy val 优化了计算过程。在示例中,我们可以观察到我们浪费了映射操作(CPU 计算),这在处理更广泛和更复杂的代码时非常昂贵。

所以在这里,惰性求值通过仅在需要时评估表达式来帮助我们优化过程,避免不必要的计算。

大数据框架 Apace Spark 在处理大量数据时大量利用了此功能。lazy val 还可以访问无限数据结构。

在 Scala 中使用 lazy val 的缺点

尽管它有优点,但它也有一些缺点,因此必须谨慎使用。

lazy val 在处理多个线程时可能会导致死锁。

示例代码:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.concurrent.duration.DurationInt

object myFirstObj {
    lazy val initialState = 50
    lazy val start = mySecondObj.initialState
}

object mySecondObj {
    lazy val initialState = myFirstObj.initialState
}

object workspace extends App {
    def run = {
        val result = Future.sequence(Seq(Future {
        myFirstObj.start
        },
        Future {
            mySecondObj.initialState
        }
        ))
        Await.result(result, 20.second)
    }
    run
}

输出:

Exception in thread "main" java.util.concurrent.TimeoutException: Future timed out after [20 seconds]
    at scala.concurrent.impl.Promise$DefaultPromise.tryAwait0(Promise.scala:248)
    at scala.concurrent.impl.Promise$DefaultPromise.result(Promise.scala:261)
    at scala.concurrent.Await$.$anonfun$result$1(package.scala:201)
    at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:62)
    at scala.concurrent.Await$.result(package.scala:124)
    at workspace$.run(workspace.scala:23)
    at workspace$.delayedEndpoint$workspace$1(workspace.scala:25)
    at workspace$delayedInit$body.apply(workspace.scala:14)
    at scala.Function0.apply$mcV$sp(Function0.scala:39)
    at scala.Function0.apply$mcV$sp$(Function0.scala:39)
    at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:17)
    at scala.App.$anonfun$main$1(App.scala:76)
    at scala.App.$anonfun$main$1$adapted(App.scala:76)
    at scala.collection.IterableOnceOps.foreach(IterableOnce.scala:563)
    at scala.collection.IterableOnceOps.foreach$(IterableOnce.scala:561)
    at scala.collection.AbstractIterable.foreach(Iterable.scala:926)
    at scala.App.main(App.scala:76)
    at scala.App.main$(App.scala:74)
    at workspace$.main(workspace.scala:14)
    at workspace.main(workspace.scala)

我们可以看到我们在这里得到了一个超时异常。Future 初始化 myFirstObjFirstObj 的实例在内部尝试初始化 SecondObj

此外,下一步的 Future 尝试初始化 mySecondObj。这会导致潜在的死锁情况。

此外,程序的空间复杂度可能会增加,因为我们可能必须存储/缓存大量表达式。由于程序员无法控制程序的执行,因此调试在这里可能会变得很棘手。

Author: Suraj P
Suraj P avatar Suraj P avatar

A technophile and a Big Data developer by passion. Loves developing advance C++ and Java applications in free time works as SME at Chegg where I help students with there doubts and assignments in the field of Computer Science.

LinkedIn GitHub