Suche…


Bemerkungen

Da val semantisch statisch ist, werden sie dort, wo sie im Code erscheinen, "in-place" initialisiert. Dies kann zu einem überraschenden und unerwünschten Verhalten führen, wenn es in abstrakten Klassen und Merkmalen verwendet wird.

PlusOne , wir möchten ein Merkmal namens PlusOne , das eine Inkrementierungsoperation für einen PlusOne Int . Da Int s unveränderlich sind, ist der Wert plus eins bei der Initialisierung bekannt und wird danach nie geändert. Es ist also semantisch ein val . Wenn Sie es auf diese Weise definieren, wird dies zu einem unerwarteten Ergebnis führen.

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

Egal welchen Wert i Sie konstruieren IntWrapper mit, ruft .incr auf das zurückgegebene Objekt wird immer 1 zurückkehren Dies liegt daran , die val incr in der Eigenschaft initialisiert wird, bevor die abgeleitete Klasse, und zu diesem Zeitpunkt i hat nur den Standardwert 0 (In anderen Umständen könnte es mit bevölkert werden Nil , null oder einem ähnlichen Standard.)

Die allgemeine Regel, dann ist die Verwendung zu vermeiden val auf einen beliebigen Wert, der auf einem abstrakten Feld abhängt. Verwenden Sie stattdessen lazy val , das nicht ausgewertet wird, bis es benötigt wird, oder def , das jedes Mal, wenn es aufgerufen wird, ausgewertet wird. Beachten Sie jedoch, dass derselbe Fehler auftritt, wenn der lazy val vor Abschluss der Initialisierung durch einen val erzwungen wird.

Eine Geige (in Scala-Js geschrieben, aber das gleiche Verhalten gilt) kann hier gefunden werden.

Var, Val und Def

var

Ein var ist eine Referenzvariable, ähnlich wie Variablen in Sprachen wie Java. Verschiedene Objekte können einem var frei zugewiesen werden, solange das angegebene Objekt denselben Typ hat, mit dem das var deklariert wurde:

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"
       ^

Hinweis in dem obigen Beispiel der Art des var wurde von der der ersten Wertzuweisung gegeben Compiler abgeleitet.

val

Ein val ist eine konstante Referenz. Daher kann ein neues Objekt nicht einem bereits zugewiesenen val zugewiesen werden.

scala> val y = 1
y: Int = 1

scala> y = 2
<console>:12: error: reassignment to val
       y = 2
         ^

Das Objekt, auf das ein val zeigt, ist jedoch nicht konstant. Dieses Objekt kann geändert werden:

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

Ein def definiert eine Methode. Eine Methode kann nicht erneut zugewiesen werden.

scala> def z = 1
z: Int

scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
       z = 2
       ^

In den obigen Beispielen geben val y und def z den gleichen Wert zurück. Jedoch ist ein def wird ausgewertet , wenn es heißt, während ein val oder var ausgewertet wird , wenn er zugeordnet ist. Dies kann zu unterschiedlichem Verhalten führen, wenn die Definition Nebenwirkungen hat:

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

Funktionen

Da Funktionen Werte sind, können sie val / var / def zugewiesen werden. Alles andere funktioniert genauso wie oben:

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

lazy val ist eine Sprachfunktion, bei der die Initialisierung eines val verzögert wird, bis zum ersten Mal darauf zugegriffen wird. Danach verhält es sich wie ein normaler val .

Fügen Sie das lazy Schlüsselwort vor val . Verwenden Sie zum Beispiel die 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

Dieses Beispiel zeigt die Ausführungsreihenfolge. Wenn die lazy val deklariert wird, ist alles , was auf die gespeicherte foo Wert ist ein fauler Funktionsaufruf , die noch nicht ausgewertet wurde. Wenn die reguläre val gesetzt ist, sehen wir der println Anruf ausführen und der Wert wird zugewiesen bar . Wenn wir foo das erste Mal println , sehen wir println ausführen - aber nicht, wenn es das zweite Mal ausgewertet wird. Ebenso, wenn bar ausgewertet wir nicht sehen println ausführen - nur , wenn er erklärt.

Wann "faul" verwendet werden

  1. Die Initialisierung ist rechenintensiv und die Verwendung von val ist selten.

    lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum}
    if (scala.util.Random.nextInt > 1000) {
      println(tiresomeValue)
    }
    

    tiresomeValue die Berechnung von tiresomeValue eine lange Zeit erforderlich, und er wird nicht immer verwendet. Ein lazy val erspart unnötige Berechnungen.

  2. Zyklische Abhängigkeiten auflösen

    Schauen wir uns ein Beispiel mit zwei Objekten an, die während der Instantiierung gleichzeitig deklariert werden müssen:

    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")
    }
    

    Ohne das Schlüsselwort lazy können die jeweiligen Objekte keine Mitglieder eines Objekts sein. Die Ausführung eines solchen Programms würde zu einer java.lang.NullPointerException . Durch die Verwendung von lazy kann die Referenz vor der Initialisierung zugewiesen werden, ohne einen uninitialisierten Wert zu befürchten.

Überladen Def

Sie können einen def überladen, wenn die Signatur unterschiedlich ist:

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'"

Dies funktioniert gleich, ob innerhalb von Klassen, Eigenschaften, Objekten oder nicht.

Benannte Parameter

Beim Aufrufen eines def können Parameter explizit nach Namen zugewiesen werden. Dies bedeutet, dass sie nicht korrekt angeordnet werden müssen. Definieren Sie beispielsweise printUs() als:

// print out the three arguments in order.
def printUs(one: String, two: String, three: String) = 
   println(s"$one, $two, $three")

Nun kann es unter anderem auf folgende Weise aufgerufen werden:

printUs("one", "two", "three") 
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two") 

Dies führt dazu one, two, three dass in allen Fällen one, two, three gedruckt werden.

Wenn nicht alle Argumente benannt werden, stimmen die ersten Argumente überein. Auf ein benanntes Argument darf kein positionelles (nicht benanntes) Argument folgen:

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow