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

Iva*_*van 64 scala properties lazy-evaluation

我是否理解这一点

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

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

  • val 进入执行范围后进行评估?

om-*_*nom 90

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

这是一个例子:

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
Run Code Online (Sandbox Code Playgroud)

  • @Ivan我怀疑这会降低性能导致延迟val [在代码中翻译](http://stackoverflow.com/q/3041253/298389)并进行双重检查以提供线程安全性.可能还有其他原因. (5认同)
  • @Ivan就在几天前我已经介绍了这个案例,当时使用lazy vals在代码中引入了死锁.注意在多线程代码中使用延迟val.至于你的最新问题,我想是的,在某些情况下它不仅是一个高性能的解决方案,而且更具可读性的IMO(因为阅读此代码的人将会意识到这是*init一次*代码片段). (4认同)
  • 好的。谢谢。这是关于“lazy val”与“val”的想法。另一个问题是“lazy val”与“def”。我有一个不可变的类,其中一些属性是构造函数中传递的数据的纯函数。将它们公开为惰性 val 而不是 defs(考虑到结果是一小段数据(如整数值)但需要一些时间来计算)不是一个好主意吗? (2认同)

Owe*_*wen 51

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

def foo() {
    new {
        val a: Any = sys.error("b is " + b)
        val b: Any = sys.error("a is " + a)
    }
}
Run Code Online (Sandbox Code Playgroud)

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


obl*_*ion 28

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

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

一个).VAL

scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val
Run Code Online (Sandbox Code Playgroud)

这里执行println是因为这里计算了result1的值.所以,现在result1将始终引用其值,即"返回val".

scala> result1
res0: String = returns val
Run Code Online (Sandbox Code Playgroud)

所以,现在,你可以看到result1现在引用它的值.请注意,此处不执行println语句,因为result1的值在第一次执行时已经计算过.因此,从现在开始,result1将始终返回相同的值,并且println语句将永远不会再次执行,因为已经执行了获取result1的值的计算.

B).懒惰的

scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>
Run Code Online (Sandbox Code Playgroud)

正如我们在这里看到的,println语句不会在这里执行,也没有计算出值.这是懒惰的本质.

现在,当我第一次引用result2时,将执行println语句并计算和分配值.

scala> result2
hello lazy val
res1: String = returns lazy val
Run Code Online (Sandbox Code Playgroud)

现在,当我再次引用result2时,这次,我们只会看到它保存的值,并且不会执行println语句.从现在开始,result2将简单地表现为val并始终返回其缓存值.

scala> result2
res2: String = returns lazy val
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

  • v很好的例子!! (2认同)
  • 这是最好的解释.应该有更多的投票. (2认同)

Mal*_*off 11

一个很好的理由选择defval,尤其是在抽象类(或者是用来模仿Java的接口特征),是,你可以覆盖def一个val子类,而不是倒过来.

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


Tra*_*own 6

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

从"3.3.1方法类型"(for def):

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

从"4.1价值声明和定义":

值定义val x : T = e定义x为评估结果的值的名称e.

惰性值定义e在第一次访问值时评估其右侧.


Jes*_*per 5

def定义一个方法。当您调用该方法时,该方法当然会运行。

val定义一个值(不可变变量)。初始化值时会计算赋值表达式。

lazy val定义一个具有延迟初始化的值。它会在第一次使用时被初始化,因此赋值表达式将在那时被计算。