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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9449474/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 03:55:27  来源:igfitidea点击:

`def` vs `val` vs `lazy val` evaluation in Scala

scalapropertieslazy-evaluation

提问by Ivan

Am I right understanding that

我是否正确理解

  • defis evaluated every time it gets accessed

  • lazy valis evaluated once it gets accessed

  • valis evaluated once it gets into the execution scope?

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

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

  • val一旦进入执行范围就被评估?

采纳答案by Owen

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". bis never evaluated and its error is never thrown. But it is in scope as soon as control enters the block.

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

回答by om-nom-nom

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

回答by oblivion

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. So, now result1 will always refer to its value i.e "returns val".

这里执行 println 是因为这里已经计算了 result1 的值。因此,现在 result1 将始终引用其值,即“返回 val”。

scala> result1
res0: String = returns val

So, now, you can see that result1 now refers to its value. 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. 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 的值。因此,从现在开始,result1 将始终返回相同的值,并且 println 语句将永远不会再次执行,因为获取 result1 值的计算已经执行。

B). lazy val

B)。懒惰的价值

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. This is the nature of lazyness.

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

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. From now on, result2 will simply behave like a val and return its cached value all the time.

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

scala> result2
res2: String = returns lazy val

C). def

C)。定义

In case of def, the result will have to be computed everytime result3 is called. 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.

在 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

回答by Malte Schwerhoff

One good reason for choosing defover val, especially in abstract classes (or in traits that are used to mimic Java's interfaces), is, that you can override a defwith a valin subclasses, but not the other way round.

选择defover 的一个很好的理由val,尤其是在抽象类(或用于模仿 Java 接口的 trait)中,是您可以在子类def中用 a覆盖 a val,但不能反过来。

Regarding lazy, there are two things I can see that one should have in mind. The first is that lazyintroduces 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. The other problem with lazyis 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,我可以看到有两件事应该牢记在心。第一个是lazy引入了一些运行时开销,但我想您需要对您的特定情况进行基准测试,以确定这是否确实对运行时性能产生了重大影响。另一个问题lazy是它可能会延迟引发异常,这可能会使您更难推理您的程序,因为异常不会预先抛出,而只会在第一次使用时抛出。

回答by Travis Brown

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 = edefines xas a name of the value that results from the evaluation of e.

A lazy value definition evaluates its right hand side ethe first time the value is accessed.

值定义val x : T = e定义x为对 的求值产生的值的名称e

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

回答by Jesper

defdefines a method. When you call the method, the method ofcourse runs.

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

valdefines a value (an immutable variable). The assignment expression is evaluated when the value is initialized.

val定义一个值(一个不可变的变量)。赋值表达式在值被初始化时被评估。

lazy valdefines a value with delayed initialization. It will be initialized when it's first used, so the assignment expression will be evaluated then.

lazy val定义一个延迟初始化的值。它将在第一次使用时进行初始化,因此将评估赋值表达式。

回答by kmos.w

A name qualified by def is evaluated by replacing the name and its RHS expression every time the name appears in the program. Therefore, this replacement will be executed every where the name appears in your program.

每次名称出现在程序中时,都会通过替换名称及其 RHS 表达式来评估由 def 限定的名称。因此,此替换将在程序中出现该名称的每个位置执行。

A name qualified by val is evaluated immediately when control reaches its RHS expression. Therefore, every time the name appears in the expression, it will be seen as the value of this evaluation.

当控制到达其 RHS 表达式时,立即评估由 val 限定的名称。因此,每次表达式中出现名称时,都会将其视为本次求值的值。

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 仅在控件到达该名称第一次使用的点时才被评估

回答by virtualeyes

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

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; that, or a val that is itself an anonymous function which retrieves runtime data (although the latter seems a bit more edge case)

因此,根据访问/计算的开销,def 或lazy val 是运行时确定的值的合适选择;那,或者一个 val 本身就是一个匿名函数,它检索运行时数据(尽管后者似乎有点边缘情况)