Qual è la differenza tra a var
e la val
definizione in Scala e perché la lingua ha bisogno di entrambi? Perché dovresti scegliere aval
over a var
e viceversa?
Qual è la differenza tra a var
e la val
definizione in Scala e perché la lingua ha bisogno di entrambi? Perché dovresti scegliere aval
over a var
e viceversa?
Risposte:
Come molti altri hanno già detto, l'oggetto assegnato a una val
non può essere sostituito e l'oggetto assegnato a una var
lattina. Tuttavia, detto oggetto può avere il suo stato interno modificato. Per esempio:
class A(n: Int) {
var value = n
}
class B(n: Int) {
val value = new A(n)
}
object Test {
def main(args: Array[String]) {
val x = new B(5)
x = new B(6) // Doesn't work, because I can't replace the object created on the line above with this new one.
x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
x.value.value = 6 // Works, because A.value can receive a new object.
}
}
Quindi, anche se non possiamo cambiare l'oggetto assegnato x
, potremmo cambiare lo stato di quell'oggetto. Alla radice, tuttavia, c'era unvar
.
Ora, l'immutabilità è una buona cosa per molte ragioni. Innanzitutto, se un oggetto non cambia lo stato interno, non devi preoccuparti se un'altra parte del codice lo sta modificando. Per esempio:
x = new B(0)
f(x)
if (x.value.value == 0)
println("f didn't do anything to x")
else
println("f did something to x")
Ciò diventa particolarmente importante con i sistemi multithread. In un sistema multithread, può succedere quanto segue:
x = new B(1)
f(x)
if (x.value.value == 1) {
print(x.value.value) // Can be different than 1!
}
Se si utilizza val
esclusivamente e si utilizzano solo strutture di dati immutabili (vale a dire, evitare array, tutto in scala.collection.mutable
ecc.), Si può essere certi che ciò non accadrà. Cioè, a meno che non ci sia del codice, forse persino un framework, che fa trucchi di riflessione - la riflessione può cambiare i valori "immutabili", sfortunatamente.
Questa è una ragione, ma c'è un'altra ragione. Quando lo usi var
, puoi essere tentato di riutilizzare lo stesso var
per diversi scopi. Questo ha alcuni problemi:
In poche parole, l'utilizzo val
è più sicuro e porta a un codice più leggibile.
Possiamo, quindi, andare nella direzione opposta. Se val
è meglio, perché averlo var
? Bene, alcune lingue hanno preso quella strada, ma ci sono situazioni in cui la mutabilità migliora molto le prestazioni.
Ad esempio, prendi un immutabile Queue
. Quando uno enqueue
o più dequeue
cose, ottieni un nuovo Queue
oggetto. In che modo, ti occuperesti di elaborare tutti gli elementi in esso?
Lo esaminerò con un esempio. Diciamo che hai una coda di cifre e vuoi comporre un numero da loro. Ad esempio, se ho una coda con 2, 1, 3, in questo ordine, voglio recuperare il numero 213. Prima risolviamolo con un mutable.Queue
:
def toNum(q: scala.collection.mutable.Queue[Int]) = {
var num = 0
while (!q.isEmpty) {
num *= 10
num += q.dequeue
}
num
}
Questo codice è veloce e facile da capire. Il suo principale svantaggio è che la coda che viene passata viene modificata da toNum
, quindi è necessario crearne una copia in anticipo. Questo è il tipo di gestione degli oggetti da cui l'immutabilità ti rende libero.
Ora, convertiamolo in un immutable.Queue
:
def toNum(q: scala.collection.immutable.Queue[Int]) = {
def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
if (qr.isEmpty)
num
else {
val (digit, newQ) = qr.dequeue
recurse(newQ, num * 10 + digit)
}
}
recurse(q, 0)
}
Poiché non riesco a riutilizzare alcune variabili per tenere traccia delle mie num
, come nell'esempio precedente, devo ricorrere alla ricorsione. In questo caso, si tratta di una ricorsione della coda, che ha prestazioni piuttosto buone. Ma non è sempre così: a volte non esiste una buona soluzione di ricorsione della coda (leggibile, semplice).
Nota, tuttavia, che posso riscrivere quel codice per usare un immutable.Queue
e un var
allo stesso tempo! Per esempio:
def toNum(q: scala.collection.immutable.Queue[Int]) = {
var qr = q
var num = 0
while (!qr.isEmpty) {
val (digit, newQ) = qr.dequeue
num *= 10
num += digit
qr = newQ
}
num
}
Questo codice è ancora efficiente, non richiede ricorsione e non devi preoccuparti se devi fare una copia della coda o meno prima di chiamare toNum
. Naturalmente, ho evitato di riutilizzare le variabili per altri scopi e nessun codice esterno a questa funzione le vede, quindi non devo preoccuparmi che i loro valori cambino da una riga all'altra, tranne quando lo faccio esplicitamente.
Scala ha optato per lasciare che il programmatore lo facesse, se il programmatore lo ritenesse la soluzione migliore. Altre lingue hanno scelto di rendere difficile tale codice. Il prezzo che Scala (e qualsiasi linguaggio con mutevole mutabilità) paga è che il compilatore non ha un margine di manovra nell'ottimizzare il codice come potrebbe altrimenti. La risposta di Java a questo è l'ottimizzazione del codice basato sul profilo di runtime. Potremmo continuare a parlare di pro e contro di entrambe le parti.
Personalmente, penso che Scala trovi il giusto equilibrio, per ora. Di gran lunga non è perfetto. Penso che sia Clojure che Haskell abbiano nozioni molto interessanti non adottate da Scala, ma Scala ha anche i suoi punti di forza. Vedremo cosa succederà nel futuro.
q
. Fa una copia - nello stack, non nell'heap - del riferimento a quell'oggetto. Per quanto riguarda le prestazioni, dovrai essere più chiaro di cosa "parli" di cui stai parlando.
(x::xs).drop(1)
è esattamentexs
una "copia" di xs
) da qui il link che ho capito. tnx!
qr
è una coda immutabile, ogni volta che l'espressione qr.dequeue
viene chiamata crea un new Queue
(vedi < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ).
val
è finale, cioè non può essere impostato. Pensa final
a Java.
val
variabili sono immutabili, ma gli oggetti a cui fanno riferimento non devono essere. Secondo il link pubblicato da Stefan: "Qui i riferimenti ai nomi non possono essere cambiati per puntare a un array diverso, ma l'array stesso può essere modificato. In altre parole, i contenuti / elementi dell'array possono essere modificati." Quindi è come final
funziona in Java.
+=
a una hashmap mutevole definita come val
giusta, credo proprio come funzioni final
in Java
In parole povere:
var = var iable
val = v ariable + fin al
La differenza è che a var
può essere riassegnato a mentre a val
non può. La mutabilità, o altrimenti di qualsiasi cosa sia effettivamente assegnata, è un problema secondario:
import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.
Mentre:
val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.
E quindi:
val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.
Se stai costruendo una struttura di dati e tutti i suoi campi sono val
s, allora quella struttura di dati è quindi immutabile, poiché il suo stato non può cambiare.
val
significa immutabile e var
significa mutevole.
Pensando in termini di C ++,
val x: T
è analogo al puntatore costante a dati non costanti
T* const x;
mentre
var x: T
è analogo al puntatore non costante ai dati non costanti
T* x;
favorendo val
overvar
aumenta immutabilità del codice di base che può facilitare la sua correttezza, concorrenza e comprensibilità.
Per comprendere il significato di avere un puntatore costante a dati non costanti, prendere in considerazione il seguente frammento di Scala:
val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)
Qui il "puntatore" val m
è costante, quindi non possiamo riassegnarlo per indicare qualcos'altro
m = n // error: reassignment to val
tuttavia possiamo davvero cambiare i dati non costanti stessi che m
puntano a così
m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
"val significa immutabile e var significa mutevole".
Per parafrasare, "val significa valore e var significa variabile".
Una distinzione che risulta estremamente importante nell'informatica (perché quei due concetti definiscono l'essenza stessa della programmazione) e che OO è riuscito a sfocare quasi completamente, perché in OO l'unico assioma è che "tutto è un oggetto". E di conseguenza, molti programmatori oggigiorno tendono a non capire / apprezzare / riconoscere, perché sono stati sottoposti al lavaggio del cervello nel "pensare alla maniera OO" esclusivamente. Spesso conduce a oggetti variabili / mutabili usati come ovunque , quando oggetti valore / immutabili potrebbero / sarebbero stati spesso migliori.
val significa immutabile e var significa mutevole
puoi pensare val
come final
mondo chiave del linguaggio di programmazione Java o mondo chiave del linguaggio c ++ const
。
Val
significa che è definitivo , non può essere riassegnato
Considerando che, Var
può essere riassegnato più tardi .
È semplice come il suo nome.
var significa che può variare
val significa invariabile
Val: i valori sono costanti di memoria tipizzate. Una volta creato, il suo valore non può essere riassegnato. un nuovo valore può essere definito con la parola chiave val.
per esempio. val x: Int = 5
Qui il tipo è facoltativo in quanto scala può inferirlo dal valore assegnato.
Var - le variabili sono unità di memoria tipizzate a cui è possibile assegnare nuovamente valori purché lo spazio di memoria sia riservato.
per esempio. var x: Int = 5
I dati memorizzati in entrambe le unità di archiviazione vengono automaticamente allocati da JVM una volta che non sono più necessari.
In scala i valori sono preferiti alle variabili a causa della stabilità, ciò porta al codice in particolare nel codice simultaneo e multithread.
Sebbene molti abbiano già risposto alla differenza tra Val e var . Ma un punto da notare è che val non è esattamente come finale parola chiave .
Possiamo cambiare il valore di val usando la ricorsione, ma non possiamo mai cambiare il valore di final. Il finale è più costante di Val.
def factorial(num: Int): Int = {
if(num == 0) 1
else factorial(num - 1) * num
}
I parametri del metodo sono di default val e ad ogni chiamata il valore viene modificato.
var qr = q
fa una copiaq
?