簡體   English   中英

Scala 中的“def”與“val”與“lazy val”評估

[英]`def` vs `val` vs `lazy val` evaluation in Scala

我是否正確理解

  • 每次訪問時都會評估def

  • lazy val一旦被訪問就會被評估

  • val一旦進入執行范圍就被評估?

是的,但有一個很好的技巧:如果您有惰性值,並且在第一次評估期間它會得到一個異常,下次您嘗試訪問它時將嘗試重新評估自己。

這是示例:

scala> import io.Source
import io.Source

scala> class Test {
     | lazy val foo = Source.fromFile("./bar.txt").getLines
     | }
defined class Test

scala> val baz = new Test
baz: Test = Test@ea5d87

//right now there is no bar.txt

scala> baz.foo
java.io.FileNotFoundException: ./bar.txt (No such file or directory)
    at java.io.FileInputStream.open(Native Method)
    at java.io.FileInputStream.<init>(FileInputStream.java:137)
...

// now I've created empty file named bar.txt
// class instance is the same

scala> baz.foo
res2: Iterator[String] = empty iterator

是的,雖然對於第三個,我會說“執行該語句時”,因為,例如:

def foo() {
    new {
        val a: Any = sys.error("b is " + b)
        val b: Any = sys.error("a is " + a)
    }
}

這給出了"b is null" b永遠不會被評估並且它的錯誤永遠不會被拋出。 但是一旦控制進入塊,它就在范圍內。

我想通過我在 REPL 中執行的示例來解釋差異。我相信這個簡單的示例更容易掌握並解釋概念差異。

在這里,我創建了一個 val result1、一個惰性 val result2 和一個 def result3,每個都具有一個 String 類型。

一種)。

scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val

這里執行 println 是因為這里已經計算了 result1 的值。 因此,現在 result1 將始終引用其值,即“返回 val”。

scala> result1
res0: String = returns val

所以,現在,您可以看到 result1 現在指的是它的值。 注意,這里沒有執行 println 語句,因為第一次執行時已經計算了 result1 的值。 因此,從現在開始,result1 將始終返回相同的值,並且 println 語句將永遠不會再次執行,因為獲取 result1 值的計算已經執行。

B)。 懶惰的價值

scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>

正如我們在這里看到的,這里沒有執行 println 語句,也沒有計算值。 這就是懶惰的本質。

現在,當我第一次引用 result2 時,將執行 println 語句並計算和分配值。

scala> result2
hello lazy val
res1: String = returns lazy val

現在,當我再次引用 result2 時,這一次,我們只會看到它保存的值,而不會執行 println 語句。 從現在開始, result2 將簡單地表現得像一個 val 並一直返回它的緩存值。

scala> result2
res2: String = returns lazy val

C)。 定義

在 def 的情況下,每次調用 result3 時都必須計算結果。 這也是我們在 scala 中將方法定義為 def 的主要原因,因為方法每次在程序內部調用時都必須計算並返回一個值。

scala> def result3 = {println("hello def"); "returns def"}
result3: String

scala> result3
hello def
res3: String = returns def

scala> result3
hello def
res4: String = returns def

選擇def不是val一個很好的理由,尤其是在抽象類(或用於模仿 Java 接口的 trait)中,是您可以在子類中用val覆蓋def ,但不能反過來。

關於lazy ,我可以看到有兩件事應該牢記在心。 第一個是lazy引入了一些運行時開銷,但我想您需要對您的特定情況進行基准測試,以確定這是否真的對運行時性能產生了重大影響。 lazy的另一個問題是它可能會延遲引發異常,這可能會使您的程序更難推理,因為異常不會預先拋出,而只會在第一次使用時拋出。

你是對的。 對於來自規范的證據:

來自“3.3.1 方法類型”(對於def ):

每次引用無參數方法名稱時都會重新評估的無參數方法名稱表達式。

來自“4.1 值聲明和定義”:

值定義val x : T = ex定義為對e求值所得的值的名稱。

惰性值定義在第一次訪問該值時評估其右側e

def定義了一個方法。 當您調用該方法時,該方法當然會運行。

val定義一個值(一個不可變的變量)。 賦值表達式在值被初始化時被評估。

lazy val定義了一個延遲初始化的值。 它將在第一次使用時進行初始化,因此將評估賦值表達式。

每次名稱出現在程序中時,都會通過替換名稱及其 RHS 表達式來評估由 def 限定的名稱。 因此,此替換將在程序中出現該名稱的每個位置執行。

當控制到達其 RHS 表達式時,立即評估由 val 限定的名稱。 因此,每次表達式中出現名稱時,都會將其視為本次求值的值。

由惰性 val 限定的名稱遵循與 val 限定相同的策略,不同之處在於它的 RHS 僅在控件到達該名稱第一次使用的點時才被評估

應該指出在使用直到運行時才知道的值時使用 val 的潛在缺陷。

request: HttpServletRequest為例request: HttpServletRequest

如果你要說:

val foo = request accepts "foo"

val初始化時,您會得到一個空指針異常, request 沒有 foo (只能在運行時知道)。

因此,根據訪問/計算的開銷,def 或lazy val 是運行時確定的值的合適選擇; 那,或者一個 val 本身就是一個匿名函數,它檢索運行時數據(盡管后者似乎有點邊緣情況)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM