サーチ…


備考

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 。 (他の条件では、 Nilnullなどの既定値が設定されている可能性があります)。

一般的な規則は、抽象フィールドに依存する任意の値に対して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 ydef 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が宣言されているときにのみ表示されます。

'怠け者'を使うべきとき

  1. 初期化は計算コストが高く、 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. 循環依存関係の解決

    インスタンス化中に同時に宣言する必要がある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.NullPointerExceptionlazyを使用することで、初期化前に参照を割り当てることができます。初期化されていない値を持つことを心配する必要はありません。

オーバーロード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'


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow