首页 文章

在斯卡拉的`def` vs`val` vs`lazy val`评估

提问于
浏览
59

我是否理解这一点

每次访问时都会评估

  • def

一旦访问

  • lazy val 就会被评估

  • val 一旦进入执行范围就会被评估?

8 回答

  • 3

    def 定义了一种方法 . 当您调用该方法时,该方法会运行 .

    val 定义了一个值(一个不可变的变量) . 初始化值时将评估赋值表达式 .

    lazy val 定义了具有延迟初始化的值 . 它将在首次使用时进行初始化,因此将对赋值表达式进行求值 .

  • 1

    选择 def 超过 val 的一个很好的理由,特别是在抽象类(或用于模仿Java接口的特性)中,您可以在子类中使用 val 覆盖 def ,但不能反过来 .

    关于 lazy ,我可以看到有两件事应该考虑到 . 第一个是 lazy 引入了一些运行时开销,但我想您需要对特定情况进行基准测试,以确定这是否真的对运行时性能产生了重大影响 . lazy 的另一个问题是,它可能会延迟引发异常,这可能会使您更难推理您的程序,因为异常不是在事先抛出,而是仅在首次使用时抛出 .

  • 86

    你是对的 . 来自the specification的证据:

    从"3.3.1 Method Types"(对于 def ):

    无参数方法指定每次引用无参数方法名称时重新计算的表达式 .

    从“4.1 Value 声明和定义”:

    值定义val x:T = e将x定义为e的评估得出的值的名称 . 延迟值定义在第一次访问值时评估其右侧 .

  • 2

    是的,虽然对于第三个我会说“当该语句被执行时”,因为,例如:

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

    这给了 "b is null" . b 永远不会被评估,它的错误永远不会被抛出 . 但是一旦控制进入区块,它就在范围内 .

  • 6

    每次名称出现在程序中时,通过替换名称及其RHS表达式来评估def限定的名称 . 因此,每次名称出现在程序中时都会执行此替换 .

    当控件达到其RHS表达式时,将立即评估由val限定的名称 . 因此,每次名称出现在表达式中时,都会将其视为此评估的值 .

    由lazy val限定的名称遵循与val限定相同的策略,但仅在控件到达第一次使用名称的点时才会评估其RHS .

  • 49

    应该指出在处理运行时未知的值时使用val的潜在缺陷 .

    举个例子, request: HttpServletRequest

    如果你要说:

    val foo = request accepts "foo"
    

    在初始化val时,你会得到一个空指针异常,请求没有foo(只能在运行时知道) .

    因此,根据访问/计算的开销,def或lazy val是运行时确定值的适当选择;那个或者一个val本身就是一个检索运行时数据的匿名函数(尽管后者似乎有点边缘情况)

  • 10

    是的,但有一个很好的伎俩:如果你有懒惰的 Value ,并且在第一次评估期间它会得到一个例外,下次你试图访问它时会尝试重新评估自己 .

    这是一个例子:

    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
    
  • 21

    我想通过我在REPL中执行的示例来解释差异 . 我相信这个简单的例子更容易理解并解释了概念差异 .

    在这里,我创建了一个val result1,一个lazy val result2和一个def result3,每个都有一个String类型 .

    A). val

    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). lazy val

    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 在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
    

相关问题