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

我是否理解这一点

  • def每次被访问时被评估

  • 一旦获得访问, lazy val被评估

  • 一旦进入执行范围, val被评估吗?

是的,但是对于第三个,我会说“当这个陈述被执行时”,因为,例如:

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

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

是的,但有一个很好的窍门:如果你有懒惰的价值,并在第一次评估它会得到一个exception,下次你会尝试访问它将尝试重新评估自己。

这里是例子:

 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 

我想通过我在REPL中执行的例子来解释不同之处。我相信这个简单的例子更容易理解并解释概念上的差异。

在这里,我创build了一个val result1,一个延迟的val result2和一个def result3,每个都有一个Stringtypes。

一个)。 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)。 懒惰的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将简单地performance为val,并始终返回其caching的值。

 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 

selectdef over val一个很好的理由是,特别是在抽象类(或者用来模拟Java接口的特性)中,你可以用子类中的val覆盖def ,但是不能相反。

关于lazy ,有两件事情我可以看到,应该记住。 首先是lazy引入了一些运行时间的开销,但是我想你需要对你的具体情况进行基准testing,以确定这是否对运行时的性能有重大的影响。 lazy的另一个问题是,它可能会延迟引发exception,这可能会导致更难以推断你的程序,因为exception不是在前面引发,而是在第一次使用时引发。

你是对的。 来自规范的证据:

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

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

从“4.1价值声明和定义”:

值定义val x : T = ex定义为从e的评估得到的值的名称。

一个懒惰的值定义在第一次访问这个值的时候评估它的右边。

def定义了一个方法。 当你调用这个方法的时候,就会运行这个方法。

val定义了一个值(一个不可变的variables)。 赋值expression式在初始化的时候被赋值。

lazy val定义了一个具有延迟初始化的值。 它将在第一次使用时被初始化,所以赋值expression式将被评估。

每当名称出现在程序中时,通过replace名称和RHSexpression式来评估由def限定的名称。 因此,这个replace将在你的程序中出现名字的地方执行。

当控制达到其RHSexpression时,立即评估通过val限定的名称。 因此,每当名称出现在expression式中,就会被看作是这个评估的价值。

由lazy val限定的名称遵循与val限定条件相同的策略,只有当控制点首次使用该名称时才会评估其RHS

在使用直到运行时才知道的值时,应该指出使用val的潜在缺陷。

举个例子, request: HttpServletRequest

如果你说:

 val foo = request accepts "foo" 

你会得到一个空指针exception,因为在val初始化的时候,request没有foo(只会在运行时知道)。

因此,根据访问/计算的开销,def或lazy val是运行时确定的值的合适select; 或者是一个本身就是一个匿名函数的检索运行时数据的val(尽pipe后者看起来更有边缘情况)