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
。這會導致潛在的死鎖情況。
此外,程式的空間複雜度可能會增加,因為我們可能必須儲存/快取大量表示式。由於程式設計師無法控制程式的執行,因此除錯在這裡可能會變得很棘手。