Jaka jest różnica między a var
a val
definicją w Scali i dlaczego język potrzebuje obu? Dlaczego miałbyś wybrać val
ponad var
i odwrotnie?
Jaka jest różnica między a var
a val
definicją w Scali i dlaczego język potrzebuje obu? Dlaczego miałbyś wybrać val
ponad var
i odwrotnie?
Odpowiedzi:
Jak wielu innych powiedziało, obiektu przypisanego do val
puszki nie można zastąpić, a obiektu przypisanego do var
puszki. Jednak wymieniony obiekt może mieć zmodyfikowany stan wewnętrzny. Na przykład:
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.
}
}
Tak więc, chociaż nie możemy zmienić przypisanego obiektu x
, możemy zmienić stan tego obiektu. U jego podstaw leżało jednak var
.
Niezmienność jest dobra z wielu powodów. Po pierwsze, jeśli obiekt nie zmienia stanu wewnętrznego, nie musisz się martwić, że zmienia go jakaś inna część kodu. Na przykład:
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")
Staje się to szczególnie ważne w systemach wielowątkowych. W systemie wielowątkowym mogą się zdarzyć:
x = new B(1)
f(x)
if (x.value.value == 1) {
print(x.value.value) // Can be different than 1!
}
Jeśli używasz val
wyłącznie i używasz niezmiennych struktur danych (to znaczy unikaj tablic, wszystkiego w scala.collection.mutable
itd.), Możesz mieć pewność, że tak się nie stanie. To znaczy, chyba że jest jakiś kod, może nawet framework, wykonujący sztuczki refleksyjne - odbicie może niestety zmienić „niezmienne” wartości.
To jeden powód, ale jest inny powód. Kiedy używasz var
, możesz ulec pokusie ponownego użycia tego samego var
do wielu celów. Ma to pewne problemy:
Mówiąc prosto, używanie val
jest bezpieczniejsze i prowadzi do bardziej czytelnego kodu.
Możemy zatem pójść w innym kierunku. Jeśli tak val
jest lepiej, dlaczego var
w ogóle mają ? Cóż, niektóre języki wybrały tę drogę, ale są sytuacje, w których zmienność bardzo poprawia wydajność.
Na przykład weź niezmienny Queue
. Kiedy obaj enqueue
lub dequeue
rzeczy w nim, pojawi się nowy Queue
obiekt. Jak więc zająłby się przetwarzaniem wszystkich elementów w nim zawartych?
Przejdę przez to z przykładem. Powiedzmy, że masz kolejkę cyfr i chcesz z nich utworzyć liczbę. Na przykład, jeśli mam kolejkę z 2, 1, 3 w tej kolejności, chcę odzyskać liczbę 213. Najpierw rozwiążmy ją za pomocą mutable.Queue
:
def toNum(q: scala.collection.mutable.Queue[Int]) = {
var num = 0
while (!q.isEmpty) {
num *= 10
num += q.dequeue
}
num
}
Ten kod jest szybki i łatwy do zrozumienia. Jego główną wadą jest to, że przekazywana kolejka jest modyfikowana toNum
, dlatego należy wcześniej wykonać jej kopię. Jest to rodzaj zarządzania obiektami, od którego niezmienność cię uwalnia.
Teraz ukryjmy to w 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)
}
Ponieważ nie mogę ponownie użyć jakiejś zmiennej do śledzenia mojej num
, tak jak w poprzednim przykładzie, muszę skorzystać z rekurencji. W tym przypadku jest to rekurencja ogona, która ma całkiem niezłą wydajność. Ale nie zawsze tak jest: czasami nie ma po prostu dobrego (czytelnego, prostego) rozwiązania rekurencji ogona.
Zauważ jednak, że mogę przepisać ten kod, aby używać immutable.Queue
i var
jednocześnie! Na przykład:
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
}
Ten kod jest nadal skuteczny, nie wymaga rekurencji i nie musisz się martwić, czy musisz wykonać kopię kolejki, czy nie, zanim zadzwonisz toNum
. Oczywiście unikałem ponownego wykorzystywania zmiennych do innych celów i żaden kod poza tą funkcją ich nie widzi, więc nie muszę się martwić, że ich wartości zmieniają się z jednej linii do drugiej - z wyjątkiem przypadków, gdy jawnie to robię.
Scala zdecydował się na to, aby programista mógł to zrobić, jeśli uzna to za najlepsze rozwiązanie. Inne języki zdecydowały się utrudnić taki kod. Cena, którą płaci Scala (i każdy język o powszechnej zmienności), polega na tym, że kompilator nie ma tak dużej swobody w optymalizacji kodu, jak mógłby inaczej. Odpowiedzią Javy na to jest optymalizacja kodu w oparciu o profil wykonawczy. Moglibyśmy ciągle mówić o zaletach i wadach każdej ze stron.
Osobiście uważam, że na razie Scala osiąga właściwą równowagę. Zdecydowanie nie jest idealny. Myślę, że zarówno Clojure, jak i Haskell mają bardzo interesujące pojęcia, które nie zostały przyjęte przez Scalę, ale Scala ma również swoje zalety. Zobaczymy, co będzie w przyszłości.
q
. Robi kopię - na stosie, a nie na stercie - odwołania do tego obiektu. Jeśli chodzi o wydajność, będziesz musiał lepiej określić, o czym mówisz.
(x::xs).drop(1)
to xs
nie jest „kopia” xs
) linku, który mogłem zrozumieć. tnx!
qr
jest to kolejka niezmienna, za każdym razem, gdy qr.dequeue
wywoływane jest wyrażenie , tworzy new Queue
( a << github.com/scala/scala/blob/2.13.x/src/library/scala/collection/... ).
val
jest ostateczny, to znaczy nie można go ustawić. Pomyśl final
w java.
val
zmienne są niezmienne, ale obiekty, do których się odwołują, nie muszą być. Zgodnie z linkiem opublikowanym przez Stefana: „Nie można zmienić odwołania do nazw, aby wskazywało na inną tablicę, ale samą tablicę można modyfikować. Innymi słowy, zawartość / elementy tablicy można modyfikować”. To tak, jak final
działa w Javie.
+=
zmienioną mapę skrótów zdefiniowaną jako po val
prostu w porządku - uważam, że dokładnie tak final
działa w java
W prostych słowach:
var = var iable
val = v ariable + fin al
Różnica polega na tym, że var
można przypisać a, a val
nie można. Zmienność lub w inny sposób tego, co jest faktycznie przypisane, jest kwestią uboczną:
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.
Natomiast:
val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.
I stąd:
val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.
Jeśli budujesz strukturę danych, a wszystkie jej pola są val
s, to struktura danych jest zatem niezmienna, ponieważ jej stan nie może się zmienić.
val
oznacza niezmienny i var
oznacza zmienny.
Myślenie w kategoriach C ++,
val x: T
jest analogiczny do stałego wskaźnika do nie stałych danych
T* const x;
podczas
var x: T
jest analogiczny do niestałego wskaźnika do niestałych danych
T* x;
Faworyzowanie w val
stosunku do var
zwiększa niezmienność bazy kodu, co może ułatwić jej poprawność, współbieżność i zrozumiałość.
Aby zrozumieć znaczenie posiadania stałego wskaźnika do nietrwałych danych, rozważ następujący fragment kodu Scala:
val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)
W tym przypadku „wskaźnik” val m
jest stały, więc nie możemy go ponownie przypisać, aby wskazywał na coś innego
m = n // error: reassignment to val
możemy jednak rzeczywiście zmienić nietrwałe dane, które to m
wskazują
m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
„val oznacza niezmienny, a var oznacza zmienny.”
Parafrazując „val oznacza wartość, a var oznacza zmienną”.
Rozróżnienie, które okazuje się niezwykle ważne w informatyce (ponieważ te dwie koncepcje definiują samą istotę tego, o co chodzi w programowaniu), i że OO udało się niemal całkowicie zatrzeć, ponieważ w OO jedynym aksjomatem jest to, że „wszystko jest obiekt". I w konsekwencji wielu programistów obecnie nie rozumie / nie docenia / nie rozpoznaje, ponieważ zostali oni poddani praniu mózgu wyłącznie w celu „myślenia OO”. Często prowadzi to do wykorzystywania obiektów zmiennych / zmiennych jak wszędzie , kiedy wartości / niezmienne obiekty mogłyby / często byłyby lepsze.
val oznacza niezmienny, a var oznacza zmienny
możesz myśleć val
jak final
świat programowania w języku Java lub świat kluczy w języku C ++ const
。
Val
oznacza, że jego ostateczny , nie może być przeniesiony
Natomiast Var
może zostać ponownie przypisany później .
To tak proste, jak sama nazwa.
var oznacza, że może się różnić
val oznacza niezmienny
Val - wartości są wpisanymi stałymi pamięci. Po utworzeniu jego wartość nie może zostać ponownie przypisana. nową wartość można zdefiniować za pomocą słowa kluczowego val.
na przykład. val x: Int = 5
Tutaj typ jest opcjonalny, ponieważ scala może wywnioskować go z przypisanej wartości.
Zmienne - zmienne są wpisywanymi jednostkami pamięci, którym można ponownie przypisać wartości, o ile zarezerwowane jest miejsce w pamięci.
na przykład. var x: Int = 5
Dane przechowywane w obu jednostkach pamięci są automatycznie rozdzielane przez JVM, gdy nie są już potrzebne.
W skalach wartości są bardziej preferowane niż zmienne ze względu na stabilność, które doprowadzają do kodu, szczególnie w kodzie współbieżnym i wielowątkowym.
Chociaż wielu już odpowiedziało na różnicę między Val a var . Należy jednak zauważyć, że val nie jest dokładnie końcowym słowem kluczowym.
Możemy zmienić wartość val za pomocą rekurencji, ale nigdy nie możemy zmienić wartości końcowej. Ostateczny jest bardziej stały niż Val.
def factorial(num: Int): Int = {
if(num == 0) 1
else factorial(num - 1) * num
}
Parametry metody są domyślnie val i przy każdej zmianie wartość połączenia jest zmieniana.
var qr = q
robi kopięq
?