繁体   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