简体   繁体   English

Scala 中的“def”与“val”与“lazy val”评估

[英]`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 defines x as a name of the value that results from the evaluation of e .值定义val x : T = ex定义为对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: HttpServletRequestrequest: 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM