Scala Language
ヴァール、ヴァル、デフ
サーチ…
備考
val
は意味的に静的であるため、コード内に表示されている場合はいつでも「インプレース」で初期化されます。これは、抽象的なクラスおよび形質で使用される場合、驚くべき望ましくない挙動を引き起こす可能性があります。
例えば、ラップされたInt
増分操作を定義するPlusOne
という特性を作りたいとしましょう。 Int
は不変であるため、初期値に1を加えた値が判明し、それ以降は変更されないので、意味的にはval
です。ただし、このように定義すると、予期しない結果が発生します。
trait PlusOne {
val i:Int
val incr = i + 1
}
class IntWrapper(val i: Int) extends PlusOne
どんなにどのような値i
あなたが構築IntWrapper
呼び出し、と.incr
常に1を返します。返されたオブジェクトにこのヴァルためであるincr
拡張クラスの前に、 特色に初期化され、その時点でi
唯一のデフォルト値を持っています0
。 (他の条件では、 Nil
、 null
などの既定値が設定されている可能性があります)。
一般的な規則は、抽象フィールドに依存する任意の値に対してval
を使用することを避けることです。代わりに、必要になるまで評価されないlazy val
や、呼び出されるたびに評価されるdef
を使用します。場合ことに注意してくださいlazy val
で評価することを余儀なくされたval
初期化が完了する前に、同じエラーが発生します。
(Scala-Jsで書かれているが、同じ動作が適用される)バイディングがここにあります。
ヴァール、ヴァル、デフ
var
var
は参照変数で、Javaなどの言語の変数に似ています。指定されたオブジェクトがvar
が宣言された型と同じ型である限り、さまざまなオブジェクトをvar
に自由に割り当てることができます。
scala> var x = 1
x: Int = 1
scala> x = 2
x: Int = 2
scala> x = "foo bar"
<console>:12: error: type mismatch;
found : String("foo bar")
required: Int
x = "foo bar"
^
上記の例では、 var
の型は、最初の値の割り当てが与えられたときにコンパイラによって推測されたことに注意してください。
ヴァル
val
は定数参照です。したがって、すでに割り当てられているval
新しいオブジェクトを割り当てることはできません。
scala> val y = 1
y: Int = 1
scala> y = 2
<console>:12: error: reassignment to val
y = 2
^
しかし、 val
指すオブジェクトは一定ではありません 。そのオブジェクトは変更されるかもしれません:
scala> val arr = new Array[Int](2)
arr: Array[Int] = Array(0, 0)
scala> arr(0) = 1
scala> arr
res1: Array[Int] = Array(1, 0)
def
def
はメソッドを定義します。メソッドを再割り当てすることはできません。
scala> def z = 1
z: Int
scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
z = 2
^
上の例では、 val y
とdef z
は同じ値を返します。しかし、 def
は呼び出されたときに評価されますが 、 val
またはvar
は割り当て時に評価されます。定義に副作用があると、動作が異なることがあります。
scala> val a = {println("Hi"); 1}
Hi
a: Int = 1
scala> def b = {println("Hi"); 1}
b: Int
scala> a + 1
res2: Int = 2
scala> b + 1
Hi
res3: Int = 2
関数
関数は値であるため、 val
/ var
/ def
代入することができます。それ以外は上記と同じ方法で動作します:
scala> val x = (x: Int) => s"value=$x"
x: Int => String = <function1>
scala> var y = (x: Int) => s"value=$x"
y: Int => String = <function1>
scala> def z = (x: Int) => s"value=$x"
z: Int => String
scala> x(1)
res0: String = value=1
scala> y(2)
res1: String = value=2
scala> z(3)
res2: String = value=3
レイジーヴァル
lazy val
初期化言語機能であるval
、それが最初にアクセスされるまで延期されます。その点の後、それは通常のval
ように機能します。
これを使用するには、 val
前にlazy
キーワードを追加します。たとえば、REPLを使用して:
scala> lazy val foo = {
| println("Initializing")
| "my foo value"
| }
foo: String = <lazy>
scala> val bar = {
| println("Initializing bar")
| "my bar value"
| }
Initializing bar
bar: String = my bar value
scala> foo
Initializing
res3: String = my foo value
scala> bar
res4: String = my bar value
scala> foo
res5: String = my foo value
この例では、実行順序を示します。 lazy val
が宣言されると、まだ評価されていない遅延関数呼び出しがfoo
値に保存されます。通常のval
が設定されると、 println
呼び出しが実行され、値がbar
割り当てられbar
。はじめてfoo
評価すると、 println
実行されるのが見えますが、2回目に評価されたときは実行されません。同様に、 bar
が評価されると、 println
executeが宣言されているときにのみ表示されます。
'怠け者'を使うべきとき
初期化は計算コストが高く、
val
使用はまれです。lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum} if (scala.util.Random.nextInt > 1000) { println(tiresomeValue) }
tiresomeValue
は計算に時間がかかり、常に使用されるわけではありません。それをlazy val
にすると、不必要な計算が節約されます。循環依存関係の解決
インスタンス化中に同時に宣言する必要がある2つのオブジェクトを持つ例を見てみましょう。
object comicBook { def main(args:Array[String]): Unit = { gotham.hero.talk() gotham.villain.talk() } } class Superhero(val name: String) { lazy val toLockUp = gotham.villain def talk(): Unit = { println(s"I won't let you win ${toLockUp.name}!") } } class Supervillain(val name: String) { lazy val toKill = gotham.hero def talk(): Unit = { println(s"Let me loosen up Gotham a little bit ${toKill.name}!") } } object gotham { val hero: Superhero = new Superhero("Batman") val villain: Supervillain = new Supervillain("Joker") }
キーワード
lazy
なければ、それぞれのオブジェクトをオブジェクトのメンバーにすることはできません。そのようなプログラムを実行すると、java.lang.NullPointerException
が発生しjava.lang.NullPointerException
。lazy
を使用することで、初期化前に参照を割り当てることができます。初期化されていない値を持つことを心配する必要はありません。
オーバーロードDef
署名が異なる場合は、 def
をオーバーロードすることができます:
def printValue(x: Int) {
println(s"My value is an integer equal to $x")
}
def printValue(x: String) {
println(s"My value is a string equal to '$x'")
}
printValue(1) // prints "My value is an integer equal to 1"
printValue("1") // prints "My value is a string equal to '1'"
これは、クラス、特性、オブジェクト内であろうとなかろうと、同じように機能します。
名前付きパラメータ
def
を呼び出すときは、パラメータを明示的に名前で割り当てることができます。そうすることは、彼らが正しく注文される必要がないことを意味する。たとえば、 printUs()
を次のように定義します。
// print out the three arguments in order.
def printUs(one: String, two: String, three: String) =
println(s"$one, $two, $three")
今では、これらの方法で(他のものの中で)呼び出すことができます:
printUs("one", "two", "three")
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two")
これは、その結果one, two, three
すべての場合に印刷されています。
すべての引数に名前が付いていない場合、最初の引数は順序で一致します。指定された引数の後ろには、名前の付いていない引数はありません。
printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'