Scala中的`def` vs`val` vs`lazy val`评估

时间:2012-02-26 00:36:34

标签: scala properties lazy-evaluation

我是否正确理解

    每次访问时都会对
  • def进行评估

  • lazy val一旦被访问就会被评估

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

8 个答案:

答案 0 :(得分:88)

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

以下是示例:

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

答案 1 :(得分:49)

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

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

这会给"b is null"。永远不会评估b,并且永远不会抛出其错误。但只要控制进入区块,它就在范围内。

答案 2 :(得分:25)

我想通过我在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将始终引用它的值,即&#34;返回val&#34;。

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

在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

答案 3 :(得分:10)

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

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

答案 4 :(得分:6)

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

从“3.3.1方法类型”(对于def):

  

无参数方法命名每次重新评估的表达式   引用无参数方法名称。

来自“4.1价值声明和定义”:

  

值定义val x : T = ex定义为由此产生的值的名称   评估e

     

惰性值定义评估其右侧e的第一个   访问该值的时间。

答案 5 :(得分:3)

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

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

lazy val定义了一个延迟初始化的值。它将在首次使用时进行初始化,因此将对赋值表达式进行评估。

答案 6 :(得分:2)

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

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

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

答案 7 :(得分:1)

当使用在运行时之前未知的值时,应指出使用val的潜在缺陷。

例如,request: HttpServletRequest

如果你要说:

val foo = request accepts "foo"

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

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