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
初始化 myFirstObj
,FirstObj
的实例在内部尝试初始化 SecondObj
。
此外,下一步的 Future
尝试初始化 mySecondObj
。这会导致潜在的死锁情况。
此外,程序的空间复杂度可能会增加,因为我们可能必须存储/缓存大量表达式。由于程序员无法控制程序的执行,因此调试在这里可能会变得很棘手。