web-dev-qa-db-ja.com

Scalaでの `def` vs` val` vs `lazy val`評価

私はそれを正しく理解していますか

  • defはアクセスされるたびに評価されます

  • lazy valはアクセスされると評価されます

  • valは、実行スコープに入ると評価されますか?

64
Ivan

はい。ただし、3番目の場合、「そのステートメントが実行されるとき」と言います。なぜなら、たとえば:

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

これは与える "b is null"bは評価されず、そのエラーはスローされません。しかし、制御がブロックに入るとすぐに範囲内になります。

51
Owen

はい、しかし、1つの素晴らしいトリックがあります:遅延値があり、最初の評価中に例外が発生し、次にアクセスしようとすると、それ自体を再評価しようとします。

以下に例を示します。

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
90
om-nom-nom

REPLで実行した例を通して違いを説明したいと思います。この簡単な例を理解しやすくし、概念的な違いを説明します。

ここでは、val result1、lazy val result2、def result3を作成していますが、それぞれがString型です。

A)。 val

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がその値を参照していることがわかります。 result1の値は最初に実行されたときにすでに計算されているため、printlnステートメントはここでは実行されないことに注意してください。したがって、現在では、result1は常に同じ値を返し、result1の値を取得するための計算がすでに実行されているため、printlnステートメントは二度と実行されません。

B)。 lazy val

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

ここでわかるように、printlnステートメントはここでは実行されず、値も計算されていません。これが怠zyの性質です。

これで、初めてresult2を参照すると、printlnステートメントが実行され、値が計算されて割り当てられます。

scala> result2
hello lazy val
res1: String = returns lazy val

さて、今度はresult2を再度参照すると、今回は保持している値のみが表示され、printlnステートメントは実行されません。これ以降、result2は単にvalのように動作し、キャッシュされた値を常に返します。

scala> result2
res2: String = returns lazy val

C)。 def

Defの場合、result3が呼び出されるたびに結果を計算する必要があります。これは、メソッドがプログラム内で呼び出されるたびに値を計算して返す必要があるため、メソッドをdef in scalaと定義する主な理由です。

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
27
oblivion

defvalよりも選択する理由の1つは、特に抽象クラス(またはJavaのインターフェースを模倣するために使用される特性)で、defを次のようにオーバーライドできることです。サブクラスではvalですが、その逆はできません。

lazyに関して、心に留めておくべきことが2つあります。 1つ目は、lazyが実行時のオーバーヘッドを導入することですが、これが実際に実行時のパフォーマンスに大きな影響を与えるかどうかを確認するには、特定の状況をベンチマークする必要があると思います。 lazyのもう1つの問題は、例外の発生を遅らせる可能性があることです。これは、例外が最初に使用されたときにのみスローされるため、プログラムについて推論するのが難しくなる可能性があります.

11

あなたは正しいです。 仕様 からの証拠:

「3.3.1メソッドタイプ」から(defの場合):

パラメータレスメソッドは、パラメータレスメソッド名が参照されるたびに再評価される式に名前を付けます。

「4.1値の宣言と定義」から:

値の定義val x : T = eは、xを評価した結果の値の名前としてeを定義します。

遅延値の定義は、最初に値にアクセスしたときに右側のeを評価します。

6
Travis Brown

defはメソッドを定義します。メソッドを呼び出すと、もちろんメソッドが実行されます。

valは、値(不変変数)を定義します。値が初期化されると、割り当て式が評価されます。

lazy valは、初期化を遅らせる値を定義します。最初に使用されるときに初期化されるので、割り当て式が評価されます。

3
Jesper

Defで修飾された名前は、プログラムに名前が現れるたびに名前とそのRHS式を置き換えることにより評価されます。したがって、この置換は、プログラム内で名前が表示されるすべての場所で実行されます。

Valで修飾された名前は、制御がRHS式に達するとすぐに評価されます。したがって、式に名前が表示されるたびに、この評価の値と見なされます。

Lazy valで修飾された名前は、val修飾と同じポリシーに従いますが、そのRHSは、名前が初めて使用されるポイントにヒットした場合にのみ評価されます。

2
kmos.w

実行時まで不明な値を操作する場合、valの使用に関して潜在的な落とし穴を指摘する必要があります。

たとえば、request: HttpServletRequest

あなたが言うなら:

val foo = request accepts "foo"

valの初期化の時点でnullポインタ例外が発生します。リクエストにはfooがありません(実行時にのみ認識されます)。

そのため、アクセス/計算の費用に応じて、defかlazy valが実行時決定値の適切な選択になります。それ、またはそれ自体が実行時データを取得する匿名関数であるval(後者はもう少しEdgeのケースのようですが)

1
virtualeyes