Scala Language
Var, Val e Def
Ricerca…
Osservazioni
Poiché val
sono semanticamente statici, vengono inizializzati "sul posto" ovunque compaiano nel codice. Questo può produrre un comportamento sorprendente e indesiderato se usato in classi e tratti astratti.
Per esempio, diciamo che vorremmo creare un tratto chiamato PlusOne
che definisca un'operazione di incremento su un Int
avvolto. Poiché Int
s è immutabile, il valore più uno è noto all'inizializzazione e non verrà mai modificato in seguito, quindi semanticamente è un valore val
. Tuttavia, definirlo in questo modo produrrà un risultato inaspettato.
trait PlusOne {
val i:Int
val incr = i + 1
}
class IntWrapper(val i: Int) extends PlusOne
Non importa quale sia il valore i
si costruisce IntWrapper
con, chiamando .incr
sul oggetto restituito sarà sempre tornare 1. Questo perché la val incr
viene inizializzato nel tratto, prima della classe che estende, e in quel momento i
ha solo il valore di default 0
. (In altre condizioni, potrebbe essere popolato con Nil
, null
o un valore predefinito simile).
La regola generale, quindi, è di evitare l'uso di val
su qualsiasi valore che dipende da un campo astratto. Invece, usa lazy val
, che non valuta fino a quando non è necessario, o def
, che valuta ogni volta che viene chiamato. Si noti tuttavia che se il valore lazy val
è forzato a valutare da una val
prima che l'inizializzazione sia completata, si verificherà lo stesso errore.
Un violino (scritto in Scala-Js, ma vale lo stesso comportamento) può essere trovato qui.
Var, Val e Def
var
Una var
è una variabile di riferimento, simile alle variabili in linguaggi come Java. Oggetti diversi possono essere assegnati liberamente a una var
, purché l'oggetto dato abbia lo stesso tipo con cui la var
stata dichiarata:
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"
^
Nota nell'esempio sopra il tipo di var
stato dedotto dal compilatore dato il primo assegnamento di valore.
val
Una val
è un riferimento costante. Pertanto, un nuovo oggetto non può essere assegnato a una val
che è già stata assegnata.
scala> val y = 1
y: Int = 1
scala> y = 2
<console>:12: error: reassignment to val
y = 2
^
Tuttavia, l'oggetto a cui punta val
non è costante. Quell'oggetto può essere modificato:
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
Una def
definisce un metodo. Un metodo non può essere riassegnato a.
scala> def z = 1
z: Int
scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
z = 2
^
Negli esempi precedenti, val y
e def z
restituiscono lo stesso valore. Tuttavia, una def
viene valutata quando viene chiamata , mentre una val
o var
viene valutata quando viene assegnata . Ciò può comportare un comportamento diverso quando la definizione ha effetti collaterali:
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
funzioni
Poiché le funzioni sono valori, possono essere assegnate a val
/ var
/ def
s. Tutto il resto funziona allo stesso modo di sopra:
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
è una funzione linguistica in cui l'inizializzazione di una val
è ritardata fino a quando non viene acceduta per la prima volta. Dopo quel punto, si comporta come una normale val
.
Per usarlo aggiungi la parola chiave lazy
prima di val
. Ad esempio, utilizzando il 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
Questo esempio dimostra l'ordine di esecuzione. Quando viene dichiarato il valore lazy val
, tutto ciò che viene salvato nel valore foo
è una chiamata a funzione lenta che non è stata ancora valutata. Quando il regolare val
è impostato, vediamo il println
chiamata esecuzione e il valore viene assegnato a bar
. Quando valutiamo foo
la prima volta vediamo println
execute - ma non quando viene valutato la seconda volta. Allo stesso modo, quando viene valutata la bar
, non vediamo println
execute - solo quando è dichiarata.
Quando usare 'pigro'
L'inizializzazione è computazionalmente costosa e l'utilizzo di
val
è raro.lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum} if (scala.util.Random.nextInt > 1000) { println(tiresomeValue) }
tiresomeValue
richiede molto tempo per il calcolo e non è sempre utilizzato. Rendendolo un valorelazy val
risparmia calcoli superflui.Risoluzione delle dipendenze cicliche
Diamo un'occhiata a un esempio con due oggetti che devono essere dichiarati contemporaneamente durante l'istanziazione:
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") }
Senza la parola chiave
lazy
, i rispettivi oggetti non possono essere membri di un oggetto. L'esecuzione di tale programma comporterebbe unajava.lang.NullPointerException
. Usandolazy
, il riferimento può essere assegnato prima che venga inizializzato, senza timore di avere un valore non inizializzato.
Sovraccarico Def
Puoi sovraccaricare un def
se la firma è diversa:
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'"
Funziona allo stesso modo se all'interno di classi, tratti, oggetti o meno.
Parametri nominati
Quando si richiama una def
, i parametri possono essere assegnati esplicitamente per nome. Ciò significa che non è necessario ordinarli correttamente. Ad esempio, definire printUs()
come:
// print out the three arguments in order.
def printUs(one: String, two: String, three: String) =
println(s"$one, $two, $three")
Ora può essere chiamato in questi modi (tra gli altri):
printUs("one", "two", "three")
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two")
Ciò comporta che one, two, three
vengano stampati in tutti i casi.
Se non tutti gli argomenti sono nominati, i primi argomenti sono abbinati per ordine. Nessun argomento posizionale (non denominato) può seguire un nome:
printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'