Scala Language
Var, Val och Def
Sök…
Anmärkningar
Eftersom val
är semantiskt statiskt, initialiseras de "på plats" var de än visas i koden. Detta kan ge överraskande och oönskat beteende när det används i abstrakta klasser och drag.
Låt oss till exempel säga att vi skulle vilja göra en egenskap som heter PlusOne
som definierar en tilläggsoperation på ett inslaget Int
. Eftersom Int
är oföränderligt är värdet plus ett känt vid initialisering och kommer aldrig att ändras efteråt, så semantiskt är det en val
. Att definiera det på detta sätt kommer dock att ge ett oväntat resultat.
trait PlusOne {
val i:Int
val incr = i + 1
}
class IntWrapper(val i: Int) extends PlusOne
Oavsett vilket värde i
konstruerar IntWrapper
med kommer samtal .incr
på det returnerade objektet alltid att återvända 1. Detta beror på att val incr
initieras i egenskapen , före den utökande klassen, och vid den tiden har i
bara standardvärdet för 0
. (Under andra förhållanden kan den vara fylld med Nil
, null
eller liknande standard.)
Den allmänna regeln är då att undvika att använda val
på något värde som beror på ett abstrakt fält. Använd istället lazy val
, som inte utvärderar förrän det behövs, eller def
, som utvärderar varje gång det kallas. Observera dock att om den lazy val
tvingas utvärdera med en val
innan initieringen är klar kommer samma fel att inträffa.
En fiol (skriven i Scala-Js, men samma beteende gäller) kan hittas här.
Var, Val och Def
var
En var
är en referensvariabel, liknande variabler i språk som Java. Olika objekt kan fritt tilldelas en var
, så länge det givna objektet har samma typ som var
förklarades med:
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"
^
Observera i exemplet ovan att typen av var
utleddes av kompilatorn som fick den första värdetilldelningen.
val
En val
är en konstant referens. Således kan inte ett nytt objekt tilldelas en val
som redan har tilldelats.
scala> val y = 1
y: Int = 1
scala> y = 2
<console>:12: error: reassignment to val
y = 2
^
Men objektet som en val
pekar på är inte konstant. Detta objekt kan ändras:
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
En def
definierar en metod. En metod kan inte tilldelas till.
scala> def z = 1
z: Int
scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
z = 2
^
I exemplen ovan returnerar val y
och def z
samma värde. En def
utvärderas emellertid när den heter , medan en val
eller var
utvärderas när den tilldelas . Detta kan resultera i olika beteenden när definitionen har biverkningar:
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
funktioner
Eftersom funktioner är värden kan de tilldelas val
/ var
/ def
s. Allt annat fungerar på samma sätt som ovan:
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
är en språkfunktion där initialiseringen av en val
är försenad tills den nås för första gången. Efter den punkten fungerar det precis som en vanlig val
.
För att använda det lägg till det lazy
nyckelordet före val
. Använd till exempel 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
Detta exempel visar exekveringsorder. När den lazy val
deklareras är allt som sparas till foo
värdet ett lat funktionssamtal som ännu inte har utvärderats. När den vanliga val
är inställd ser vi att det println
samtalet körs och värdet tilldelas bar
. När vi evalute foo
första gången vi ser println
köra - men inte när det utvärderas för andra gången. På samma sätt ser vi att utskriften inte println
när bar
utvärderas - bara när det deklareras.
När man använder "lat"
Initieringen är beräkningskrävande och användningen av
val
är sällsynt.lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum} if (scala.util.Random.nextInt > 1000) { println(tiresomeValue) }
tiresomeValue
tar lång tid att beräkna, och den används inte alltid. Att göra det till enlazy val
sparar onödig beräkning.Lösa cykliska beroenden
Låt oss titta på ett exempel med två objekt som måste deklareras samtidigt under inställning:
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") }
Utan nyckelordet
lazy
kan de respektive objekten inte vara medlemmar i ett objekt. Exekvering av ett sådant program skulle resultera i enjava.lang.NullPointerException
. Genom att användalazy
kan referensen tilldelas innan den initialiseras, utan rädsla för att ha ett oinitialiserat värde.
Överbelastning Def
Du kan överbelasta en def
om signaturen är annorlunda:
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'"
Detta fungerar på samma sätt, oavsett om det finns klasser, drag, objekt eller inte.
Namngivna parametrar
När man åberopar en def
kan parametrar tilldelas uttryckligen med namn. Att göra det betyder att de inte behöver ordnas korrekt. Definiera till exempel printUs()
som:
// print out the three arguments in order.
def printUs(one: String, two: String, three: String) =
println(s"$one, $two, $three")
Nu kan det kallas på dessa sätt (bland annat):
printUs("one", "two", "three")
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two")
Detta resulterar i att one, two, three
skrivs ut i alla fall.
Om inte alla argument heter, matchas de första argumenten efter ordning. Inget positionellt (icke namngivet) argument får följa ett namngivet:
printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'