developer tip

Scala에서`def` vs`val` vs`lazy val` 평가

copycodes 2020. 11. 19. 21:41
반응형

Scala에서`def` vs`val` vs`lazy val` 평가


내가 이해하는 것이 맞습니까?

  • def 액세스 할 때마다 평가됩니다.

  • lazy val 액세스되면 평가됩니다.

  • val 실행 범위에 들어가면 평가됩니까?


예,하지만 세 번째 경우에는 "언제 해당 문이 실행될 때"라고 말하겠습니다. 예를 들면 다음과 같습니다.

def foo() {
    new {
        val a: Any = sys.error("b is " + b)
        val b: Any = sys.error("a is " + a)
    }
}

이것은 "b is null". b평가되지 않으며 오류가 발생하지 않습니다. 그러나 통제권이 블록에 들어가는 즉시 범위 내에 있습니다.


예,하지만 한 가지 좋은 트릭이 있습니다. 만약 당신이 게으른 가치를 가지고 있고 처음 평가하는 동안 예외를 받게 될 것이고, 다음에 당신이 접근을 시도 할 때 스스로 재평가를 시도 할 것입니다.

예를 들면 다음과 같습니다.

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

REPL에서 실행 한 예제를 통해 차이점을 설명하고 싶은데,이 간단한 예제가 이해하기 쉽고 개념적 차이점을 설명하는 것 같습니다.

여기서는 각각 String 유형을 갖는 val result1, lazy val result2 및 def result3을 만듭니다.

ㅏ).

scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val

여기서는 result1의 값이 계산 되었기 때문에 println이 실행됩니다. 따라서 이제 result1은 항상 "returns val"이라는 값을 참조합니다.

scala> result1
res0: String = returns val

이제 result1이 값을 참조한다는 것을 알 수 있습니다. 여기서 println 문은 처음 실행될 때 result1의 값이 이미 계산 되었기 때문에 여기에서 실행되지 않습니다. 따라서 이제부터는 result1이 항상 동일한 값을 반환하고 result1 값을 얻기위한 계산이 이미 수행되었으므로 println 문이 다시 실행되지 않습니다.

비). 게으른 발

scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>

여기서 볼 수 있듯이 println 문은 여기서 실행되지 않으며 값도 계산되지 않았습니다. 이것이 게으름의 본질입니다.

Now, when i refer to the result2 for the first time, println statement will be executed and value will be computed and assigned.

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.

scala> result2
res2: String = returns lazy val

C). def

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.

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.

Regarding lazy, there are two things I can see that one should have in mind. 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. 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.


You are correct. For evidence from the specification:

From "3.3.1 Method Types" (for def):

Parameterless methods name expressions that are re-evaluated each time the parameterless method name is referenced.

From "4.1 Value Declarations and Definitions":

A value definition val x : T = e defines x as a name of the value that results from the evaluation of e.

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


def defines a method. When you call the method, the method ofcourse runs.

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

lazy val defines a value with delayed initialization. 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. 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. 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


Should point out a potential pitfall in regard to usage of val when working with values not known until runtime.

Take, for example, 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).

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)

참고URL : https://stackoverflow.com/questions/9449474/def-vs-val-vs-lazy-val-evaluation-in-scala

반응형