`Scala中的def`vs`val`vs`lazy val`求值
我的理解正确吗`Scala中的def`vs`val`vs`lazy val`求值,scala,properties,lazy-evaluation,Scala,Properties,Lazy Evaluation,我的理解正确吗 def在每次访问时都进行评估 lazy val一旦被访问就会被评估 val在进入执行范围后进行评估 是的,但对于第三个语句,我会说“当该语句被执行时”,因为,例如: def foo() { new { val a: Any = sys.error("b is " + b) val b: Any = sys.error("a is " + a) } } 这将给出“b为空”b不会被计算,也不会抛出其错误。但一旦控件进入块,它就在范围
在每次访问时都进行评估def
一旦被访问就会被评估lazy val
在进入执行范围后进行评估val
def foo() {
new {
val a: Any = sys.error("b is " + b)
val b: Any = sys.error("a is " + a)
}
}
这将给出
“b为空”
b
不会被计算,也不会抛出其错误。但一旦控件进入块,它就在范围内。您是正确的。从以下方面获取证据:
从“3.3.1方法类型”(对于def
):
无参数方法命名每次重新计算的表达式
将引用无参数方法名称
从“4.1价值声明和定义”中:
值定义val x:T=e
将x
定义为从
e
的评估
惰性值定义计算其右侧e
第一个
访问该值的时间
是的,但有一个很好的技巧:如果您有一个惰性值,并且在第一次求值时它将得到一个异常,下次您尝试访问它时,它将尝试重新求值 下面是一个例子:
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
scala>导入io.Source
导入io.Source
scala>类测试{
|lazy val foo=Source.fromFile(“./bar.txt”).getLines
| }
定义类测试
scala>val baz=新测试
baz:测试=Test@ea5d87
//现在没有bar.txt
scala>baz.foo
java.io.FileNotFoundException:./bar.txt(无此类文件或目录)
在java.io.FileInputStream.open(本机方法)
位于java.io.FileInputStream。(FileInputStream.java:137)
...
//现在我已经创建了一个名为bar.txt的空文件
//类实例是相同的
scala>baz.foo
res2:迭代器[字符串]=空迭代器
def
定义了一个方法。当您调用该方法时,当然会运行该方法
val
定义一个值(不可变变量)。赋值表达式在值初始化时计算
lazy val
定义了一个初始化延迟的值。它将在第一次使用时初始化,因此随后将对赋值表达式进行求值。应该指出,在使用运行时才知道的值时,val的使用存在一个潜在的陷阱
例如,request:HttpServletRequest
如果你说:
val foo = request accepts "foo"
您将在val初始化时得到一个空指针异常,请求没有foo(只有在运行时才知道)
因此,根据访问/计算的费用,def或lazy val是运行时确定的值的适当选择;或者val本身是一个匿名函数,用于检索运行时数据(尽管后者似乎更边缘化)选择
def
而不是val
,特别是在抽象类(或用于模拟Java接口的特征)中,一个很好的理由是,您可以在子类中用val
覆盖def
,但不能反过来
关于懒惰,有两件事我可以看到,一个人应该牢记在心。第一个是
lazy
引入了一些运行时开销,但我想您需要对特定情况进行基准测试,以确定这是否对运行时性能有重大影响。lazy
的另一个问题是,它可能会延迟引发异常,这可能使您的程序更难推理,因为该异常不是预先引发的,而是仅在第一次使用时才会引发。通过在程序中每次出现名称时替换名称及其RHS表达式来计算def限定的名称。因此,此替换将在程序中名称出现的每个位置执行
当控件到达其RHS表达式时,将立即计算由val限定的名称。因此,每次名称出现在表达式中时,它都将被视为此计算的值
由lazy val限定的名称遵循与val限定相同的策略,例外情况是,只有当控件到达第一次使用名称的点时,才会对其RHS进行评估我想通过我在REPL中执行的示例来解释差异。我相信这个简单的示例更容易理解掌握并解释概念差异 在这里,我创建了一个val result1、一个lazy val result2和一个def result3,每个都有一个类型字符串 A)。val
scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val
scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>
这里执行println是因为这里计算了result1的值。因此,现在result1将始终引用其值,即“returns val”
现在,您可以看到result1现在引用了它的值。注意,这里不执行println语句,因为result1的值在第一次执行时已经计算过。所以,从现在开始,result1将始终返回相同的值,println语句将不再执行,因为已经执行了获取result1值的计算
B)。懒惰的val
scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val
scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>
现在,当我再次引用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
顺便问一下,除非需要相反的定义,否则定义所有的VAL不是一个好主意吗?@Ivan我怀疑这会减少