私はそれを正しく理解していますか
def
はアクセスされるたびに評価されます
lazy val
はアクセスされると評価されます
val
は、実行スコープに入ると評価されますか?
はい。ただし、3番目の場合、「そのステートメントが実行されるとき」と言います。なぜなら、たとえば:
def foo() {
new {
val a: Any = sys.error("b is " + b)
val b: Any = sys.error("a is " + a)
}
}
これは与える "b is null"
。 b
は評価されず、そのエラーはスローされません。しかし、制御がブロックに入るとすぐに範囲内になります。
はい、しかし、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
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
def
をval
よりも選択する理由の1つは、特に抽象クラス(またはJavaのインターフェースを模倣するために使用される特性)で、def
を次のようにオーバーライドできることです。サブクラスではval
ですが、その逆はできません。
lazy
に関して、心に留めておくべきことが2つあります。 1つ目は、lazy
が実行時のオーバーヘッドを導入することですが、これが実際に実行時のパフォーマンスに大きな影響を与えるかどうかを確認するには、特定の状況をベンチマークする必要があると思います。 lazy
のもう1つの問題は、例外の発生を遅らせる可能性があることです。これは、例外が最初に使用されたときにのみスローされるため、プログラムについて推論するのが難しくなる可能性があります.
あなたは正しいです。 仕様 からの証拠:
「3.3.1メソッドタイプ」から(def
の場合):
パラメータレスメソッドは、パラメータレスメソッド名が参照されるたびに再評価される式に名前を付けます。
「4.1値の宣言と定義」から:
値の定義
val x : T = e
は、x
を評価した結果の値の名前としてe
を定義します。遅延値の定義は、最初に値にアクセスしたときに右側の
e
を評価します。
def
はメソッドを定義します。メソッドを呼び出すと、もちろんメソッドが実行されます。
val
は、値(不変変数)を定義します。値が初期化されると、割り当て式が評価されます。
lazy val
は、初期化を遅らせる値を定義します。最初に使用されるときに初期化されるので、割り当て式が評価されます。
Defで修飾された名前は、プログラムに名前が現れるたびに名前とそのRHS式を置き換えることにより評価されます。したがって、この置換は、プログラム内で名前が表示されるすべての場所で実行されます。
Valで修飾された名前は、制御がRHS式に達するとすぐに評価されます。したがって、式に名前が表示されるたびに、この評価の値と見なされます。
Lazy valで修飾された名前は、val修飾と同じポリシーに従いますが、そのRHSは、名前が初めて使用されるポイントにヒットした場合にのみ評価されます。
実行時まで不明な値を操作する場合、valの使用に関して潜在的な落とし穴を指摘する必要があります。
たとえば、request: HttpServletRequest
あなたが言うなら:
val foo = request accepts "foo"
valの初期化の時点でnullポインタ例外が発生します。リクエストにはfooがありません(実行時にのみ認識されます)。
そのため、アクセス/計算の費用に応じて、defかlazy valが実行時決定値の適切な選択になります。それ、またはそれ自体が実行時データを取得する匿名関数であるval(後者はもう少しEdgeのケースのようですが)