Scala Language
Var, Val und Def
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
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 vontiresomeValue
eine lange Zeit erforderlich, und er wird nicht immer verwendet. Einlazy val
erspart unnötige Berechnungen.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 einerjava.lang.NullPointerException
. Durch die Verwendung vonlazy
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'