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'

  1. 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 valore lazy val risparmia calcoli superflui.

  2. 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 una java.lang.NullPointerException . Usando lazy , 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'


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow