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) }tiresomeValuetar lång tid att beräkna, och den används inte alltid. Att göra det till enlazy valsparar 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
lazykan de respektive objekten inte vara medlemmar i ett objekt. Exekvering av ett sådant program skulle resultera i enjava.lang.NullPointerException. Genom att användalazykan 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'