Scala Language
Var, Val en Def
Zoeken…
Opmerkingen
Omdat val
semantisch statisch is, worden ze 'ter plekke' geïnitialiseerd, waar ze ook in de code voorkomen. Dit kan verrassend en ongewenst gedrag veroorzaken wanneer het wordt gebruikt in abstracte klassen en eigenschappen.
Laten we bijvoorbeeld zeggen dat we een eigenschap PlusOne
die een incrementele bewerking op een ingepakte Int
definieert. Aangezien Int
's onveranderlijk zijn, is de waarde plus één bekend bij initialisatie en zal deze daarna nooit meer worden gewijzigd, dus semantisch is het een val
. Als u het op deze manier definieert, krijgt u echter een onverwacht resultaat.
trait PlusOne {
val i:Int
val incr = i + 1
}
class IntWrapper(val i: Int) extends PlusOne
Het maakt niet uit welke waarde i
u bouwen IntWrapper
met, bellen .incr
op de geretourneerde object zal altijd terugkeren 1. Dit is omdat de val incr
wordt geïnitialiseerd in de eigenschap, voor de uitbreiding van de klas, en in die tijd i
alleen de standaardwaarde 0
. (In andere omstandigheden kan het worden gevuld met Nil
, null
of een vergelijkbare standaard.)
De algemene regel is dus om te voorkomen dat val
voor elke waarde die afhankelijk is van een abstract veld. Gebruik in plaats daarvan lazy val
, die niet evalueert totdat het nodig is, of def
, die evalueert telkens wanneer het wordt aangeroepen. Merk echter op dat als de lazy val
wordt gedwongen te evalueren met een val
voordat de initialisatie is voltooid, dezelfde fout optreedt.
Een viool (geschreven in Scala-Js, maar hetzelfde gedrag is van toepassing) kan hier worden gevonden .
Var, Val en Def
var
Een var
is een referentievariabele, vergelijkbaar met variabelen in talen zoals Java. Verschillende objecten kunnen vrij worden toegewezen aan een var
, zolang het gegeven object hetzelfde type heeft waarmee de var
is gedeclareerd met:
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"
^
Merk op dat in het bovenstaande voorbeeld het type var
werd afgeleid door de compiler gezien de eerste waardetoekenning.
val
Een val
is een constante referentie. Aldus een nieuw object kan niet worden toegewezen aan een val
die reeds toegewezen.
scala> val y = 1
y: Int = 1
scala> y = 2
<console>:12: error: reassignment to val
y = 2
^
Het object waarnaar een val
verwijst, is echter niet constant. Dat object kan worden gewijzigd:
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
Een def
definieert een werkwijze. Een methode kan niet opnieuw worden toegewezen.
scala> def z = 1
z: Int
scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
z = 2
^
In de bovenstaande voorbeelden retourneren val y
en def z
dezelfde waarde. Een def
wordt echter geëvalueerd wanneer deze wordt aangeroepen , terwijl een val
of var
wordt geëvalueerd wanneer deze wordt toegewezen . Dit kan leiden tot ander gedrag wanneer de definitie bijwerkingen heeft:
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
functies
Omdat functies waarden zijn, kunnen ze worden toegewezen aan val
/ var
/ def
s. Al het andere werkt op dezelfde manier als hierboven:
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
Luie val
lazy val
is een taalfunctie waarbij de initialisatie van een val
wordt uitgesteld totdat deze voor de eerste keer wordt gebruikt. Na dat punt werkt het net als een normale val
.
Om het te gebruiken voeg je het lazy
trefwoord toe voor val
. Bijvoorbeeld met behulp van de 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
Dit voorbeeld toont de uitvoeringsvolgorde. Wanneer de lazy val
wordt gedeclareerd, is het enige dat wordt opgeslagen naar de foo
waarde een luie functieaanroep die nog niet is geëvalueerd. Wanneer de reguliere val
is ingesteld, zien we de println
oproep uit te voeren en de waarde wordt toegewezen aan bar
. Wanneer we foo
de eerste keer evalueren, zien we println
uitvoeren - maar niet wanneer het de tweede keer wordt geëvalueerd. Op dezelfde manier zien we dat wanneer bar
wordt geëvalueerd, println
uitgevoerd - alleen wanneer het wordt gedeclareerd.
Wanneer 'lui' gebruiken
Initialisatie is rekenkundig duur en het gebruik van
val
is zeldzaam.lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum} if (scala.util.Random.nextInt > 1000) { println(tiresomeValue) }
tiresomeValue
kost veel tijd om te berekenen en wordt niet altijd gebruikt. Als u er eenlazy val
bespaart u onnodige berekeningen.Cyclische afhankelijkheden oplossen
Laten we een voorbeeld bekijken met twee objecten die tegelijkertijd moeten worden gedeclareerd tijdens het instantiëren:
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") }
Zonder het trefwoord
lazy
kunnen de respectieve objecten geen lid zijn van een object. De uitvoering van een dergelijk programma zou resulteren in eenjava.lang.NullPointerException
. Doorlazy
, kan de referentie worden toegewezen voordat deze wordt geïnitialiseerd, zonder angst voor een niet-geïnitialiseerde waarde.
Overbelasting Def
U kunt een def
overbelasten als de handtekening anders is:
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'"
Dit werkt hetzelfde, of het nu binnen klassen, eigenschappen, objecten is of niet.
Benoemde parameters
Bij het aanroepen van een def
kunnen parameters expliciet op naam worden toegewezen. Dit betekent dat ze niet correct moeten worden geordend. Definieer bijvoorbeeld printUs()
als:
// print out the three arguments in order.
def printUs(one: String, two: String, three: String) =
println(s"$one, $two, $three")
Nu kan het op deze manieren (onder andere) worden genoemd:
printUs("one", "two", "three")
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two")
Dit resulteert in het afdrukken van one, two, three
in alle gevallen.
Als niet alle argumenten worden genoemd, worden de eerste argumenten op volgorde gekoppeld. Geen positioneel (niet-genoemd) argument mag op een genoemd volgen:
printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'