[英]`def` vs `val` vs `lazy val` evaluation in Scala
Am I right understanding that我是否正确理解
def
is evaluated every time it gets accessed每次访问时都会评估def
lazy val
is evaluated once it gets accessed lazy val
一旦被访问就会被评估
val
is evaluated once it gets into the execution scope? val
一旦进入执行范围就被评估?
Yes, but there is one nice trick: if you have lazy value, and during first time evaluation it will get an exception, next time you'll try to access it will try to re-evaluate itself.是的,但有一个很好的技巧:如果您有惰性值,并且在第一次评估期间它会得到一个异常,下次您尝试访问它时将尝试重新评估自己。
Here is example:这是示例:
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
Yes, though for the 3rd one I would say "when that statement is executed", because, for example:是的,虽然对于第三个,我会说“执行该语句时”,因为,例如:
def foo() {
new {
val a: Any = sys.error("b is " + b)
val b: Any = sys.error("a is " + a)
}
}
This gives "b is null"
.这给出了"b is null"
。 b
is never evaluated and its error is never thrown. b
永远不会被评估并且它的错误永远不会被抛出。 But it is in scope as soon as control enters the block.但是一旦控制进入块,它就在范围内。
I would like to explain the differences through the example that i executed in REPL.I believe this simple example is easier to grasp and explains the conceptual differences.我想通过我在 REPL 中执行的示例来解释差异。我相信这个简单的示例更容易掌握并解释概念差异。
Here,I am creating a val result1, a lazy val result2 and a def result3 each of which has a type String.在这里,我创建了一个 val result1、一个惰性 val result2 和一个 def result3,每个都具有一个 String 类型。
A).一种)。 val值
scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val
Here, println is executed because the value of result1 has been computed here.这里执行 println 是因为这里已经计算了 result1 的值。 So, now result1 will always refer to its value ie "returns val".因此,现在 result1 将始终引用其值,即“返回 val”。
scala> result1
res0: String = returns val
So, now, you can see that result1 now refers to its value.所以,现在,您可以看到 result1 现在指的是它的值。 Note that, the println statement is not executed here because the value for result1 has already been computed when it was executed for the first time.注意,这里没有执行 println 语句,因为第一次执行时已经计算了 result1 的值。 So, now onwards, result1 will always return the same value and println statement will never be executed again because the computation for getting the value of result1 has already been performed.因此,从现在开始,result1 将始终返回相同的值,并且 println 语句将永远不会再次执行,因为获取 result1 值的计算已经执行。
B). B)。 lazy val懒惰的价值
scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>
As we can see here, the println statement is not executed here and neither the value has been computed.正如我们在这里看到的,这里没有执行 println 语句,也没有计算值。 This is the nature of lazyness.这就是懒惰的本质。
Now, when i refer to the result2 for the first time, println statement will be executed and value will be computed and assigned.现在,当我第一次引用 result2 时,将执行 println 语句并计算和分配值。
scala> result2
hello lazy val
res1: String = returns lazy val
Now, when i refer to result2 again, this time around, we will only see the value it holds and the println statement wont be executed.现在,当我再次引用 result2 时,这一次,我们只会看到它保存的值,而不会执行 println 语句。 From now on, result2 will simply behave like a val and return its cached value all the time.从现在开始, result2 将简单地表现得像一个 val 并一直返回它的缓存值。
scala> result2
res2: String = returns lazy val
C). C)。 def定义
In case of def, the result will have to be computed everytime result3 is called.在 def 的情况下,每次调用 result3 时都必须计算结果。 This is also the main reason that we define methods as def in scala because methods has to compute and return a value everytime it is called inside the program.这也是我们在 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
One good reason for choosing def
over val
, especially in abstract classes (or in traits that are used to mimic Java's interfaces), is, that you can override a def
with a val
in subclasses, but not the other way round.选择def
不是val
一个很好的理由,尤其是在抽象类(或用于模仿 Java 接口的 trait)中,是您可以在子类中用val
覆盖def
,但不能反过来。
Regarding lazy
, there are two things I can see that one should have in mind.关于lazy
,我可以看到有两件事应该牢记在心。 The first is that lazy
introduces some runtime overhead, but I guess that you would need to benchmark your specific situation to find out whether this actually has a significant impact on the runtime performance.第一个是lazy
引入了一些运行时开销,但我想您需要对您的特定情况进行基准测试,以确定这是否真的对运行时性能产生了重大影响。 The other problem with lazy
is that it possibly delays raising an exception, which might make it harder to reason about your program, because the exception is not thrown upfront but only on first use. lazy
的另一个问题是它可能会延迟引发异常,这可能会使您的程序更难推理,因为异常不会预先抛出,而只会在第一次使用时抛出。
You are correct.你是对的。 For evidence from the specification :对于来自规范的证据:
From "3.3.1 Method Types" (for def
):来自“3.3.1 方法类型”(对于def
):
Parameterless methods name expressions that are re-evaluated each time the parameterless method name is referenced.每次引用无参数方法名称时都会重新评估的无参数方法名称表达式。
From "4.1 Value Declarations and Definitions":来自“4.1 值声明和定义”:
A value definition
val x : T = e
definesx
as a name of the value that results from the evaluation ofe
.值定义val x : T = e
将x
定义为对e
求值所得的值的名称。A lazy value definition evaluates its right hand side
e
the first time the value is accessed.惰性值定义在第一次访问该值时评估其右侧e
。
def
defines a method. def
定义了一个方法。 When you call the method, the method ofcourse runs.当您调用该方法时,该方法当然会运行。
val
defines a value (an immutable variable). val
定义一个值(一个不可变的变量)。 The assignment expression is evaluated when the value is initialized.赋值表达式在值被初始化时被评估。
lazy val
defines a value with delayed initialization. lazy val
定义了一个延迟初始化的值。 It will be initialized when it's first used, so the assignment expression will be evaluated then.它将在第一次使用时进行初始化,因此将评估赋值表达式。
A name qualified by def is evaluated by replacing the name and its RHS expression every time the name appears in the program.每次名称出现在程序中时,都会通过替换名称及其 RHS 表达式来评估由 def 限定的名称。 Therefore, this replacement will be executed every where the name appears in your program.因此,此替换将在程序中出现该名称的每个位置执行。
A name qualified by val is evaluated immediately when control reaches its RHS expression.当控制到达其 RHS 表达式时,立即评估由 val 限定的名称。 Therefore, every time the name appears in the expression, it will be seen as the value of this evaluation.因此,每次表达式中出现名称时,都会将其视为本次求值的值。
A name qualified by lazy val follows the same policy as that of val qualification with an exception that its RHS will be evaluated only when the control hits the point where the name is used for the first time由惰性 val 限定的名称遵循与 val 限定相同的策略,不同之处在于它的 RHS 仅在控件到达该名称第一次使用的点时才被评估
Should point out a potential pitfall in regard to usage of val when working with values not known until runtime.应该指出在使用直到运行时才知道的值时使用 val 的潜在缺陷。
Take, for example, request: HttpServletRequest
以request: HttpServletRequest
为例request: HttpServletRequest
If you were to say:如果你要说:
val foo = request accepts "foo"
You would get a null pointer exception as at the point of initialization of the val , request has no foo (would only be know at runtime).在val初始化时,您会得到一个空指针异常, request 没有 foo (只能在运行时知道)。
So, depending on the expense of access/calculation, def or lazy val are then appropriate choices for runtime-determined values;因此,根据访问/计算的开销,def 或lazy val 是运行时确定的值的合适选择; that, or a val that is itself an anonymous function which retrieves runtime data (although the latter seems a bit more edge case)那,或者一个 val 本身就是一个匿名函数,它检索运行时数据(尽管后者似乎有点边缘情况)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.