Яка різниця між визначенням var і val у Scala?


301

Яка різниця між a varта valвизначенням у Scala і чому мова потрібна обом? Чому б ви вибрали valнад varі навпаки?

Відповіді:


331

Як так багато інших говорили, об'єкт, призначений для valне може бути замінений, і об'єкт, призначений varбанку. Однак зазначений об'єкт може змінити свій внутрішній стан. Наприклад:

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.
  }
}

Отже, навіть якщо ми не можемо змінити призначений об’єкт x, ми можемо змінити стан цього об'єкта. В корені цього, однак, стояв аvar .

Тепер незмінність - це гарна річ з багатьох причин. По-перше, якщо об’єкт не змінює внутрішній стан, вам не доведеться турбуватися, чи змінить його якась інша частина вашого коду. Наприклад:

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")

Це стає особливо важливим при багатопотокових системах. У багатопотоковій системі може статися таке:

x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

Якщо ви використовуєте valвиключно і використовуєте лише незмінні структури даних (тобто уникайте масивів, все в scala.collection.mutableтощо), ви можете бути впевнені, що цього не станеться. Тобто, якщо, на жаль, не існує якогось коду, можливо, навіть фреймворка, який виконує трюкові рефлексії - відображення може змінити "незмінні" значення, на жаль.

Це одна причина, але є й інша причина. Під час використання varви можете спокуситись повторно використовувати одне varі те ж для кількох цілей. У цьому є деякі проблеми:

  • Людям, які читають код, буде складніше знати, яке значення має змінна в певній частині коду.
  • Ви можете забути повторно ініціалізувати змінну в якомусь кодовому шляху і в кінцевому підсумку передавати неправильні значення нижче за кодом.

Простіше кажучи, використання valбезпечніше і призводить до більш читабельного коду.

Тоді ми можемо піти в іншому напрямку. Якщо valце краще, то навіщо varвзагалі? Ну, деякі мови йшли цим маршрутом, але є ситуації, коли багатозмінність значно покращує продуктивність.

Наприклад, візьміть непорушний Queue. Коли ви enqueueабо dequeueвсе, що в ньому, ви отримуєте новий Queueоб'єкт. Як тоді ви б зайнялися обробкою всіх елементів у ньому?

Я пройду це на прикладі. Скажімо, у вас черга цифр, і ви хочете скласти з них число. Наприклад, якщо у мене в черзі є черга з 2, 1, 3, я хочу повернути число 213. Давайте спочатку вирішимо це за допомогою mutable.Queue:

def toNum(q: scala.collection.mutable.Queue[Int]) = {
  var num = 0
  while (!q.isEmpty) {
    num *= 10
    num += q.dequeue
  }
  num
}

Цей код швидко та легко зрозуміти. Основним його недоліком є ​​те, що чергу, яку передають, модифікується toNum, тому потрібно заздалегідь зробити її копію. Ось такий тип управління об'єктом, від якого незмінність позбавляє вас.

Тепер давайте приховаємо це до 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)
}

Тому що я не можу повторно використовувати якусь змінну, щоб відстежувати свою num , як у попередньому прикладі, мені потрібно вдатися до рекурсії. У цьому випадку це хвоста-рекурсія, яка має досить хороші показники. Але це не завжди так: іноді просто немає хорошого (читабельного, простого) рішення для рекурсії хвоста.

Однак зауважте, що я можу переписати цей код, щоб одночасно використовувати immutable.Queueі a var! Наприклад:

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
}

Цей код все ще ефективний, не вимагає рекурсії, і вам не потрібно хвилюватися, чи потрібно вам робити копію своєї черги перед тим, як дзвонити toNum. Звичайно, я уникав повторного використання змінних для інших цілей, і жоден код поза цією функцією їх не бачить, тому мені не потрібно турбуватися про зміну їх значень з одного рядка в інший - за винятком випадків, коли я це явно роблю.

Скала вирішив дозволити програмісту зробити це, якщо програміст вважає це найкращим рішенням. Інші мови вирішили ускладнити такий код. Ціна Scala (і будь-яка мова з широкомасштабною модифікацією) платить за те, що компілятор не має стільки свободи в оптимізації коду, як міг би інакше. Відповідь Java на це - оптимізація коду на основі профілю виконання. Ми можемо продовжувати питання щодо плюсів і мінусів для кожної сторони.

Особисто я вважаю, що Скала зараз справляє правильний баланс. Це далеко не ідеально. Я думаю, що і Клуджуре, і Хаскелл мають дуже цікаві поняття, не прийняті Скалою, але і у Скали є свої сильні сторони. Ми побачимо, що з’явиться в майбутньому.


Трохи пізно, але ... Чи var qr = qробить копію q?

5
@davips Це не робить копію об'єкта, на яку посилається q. Це робить копію - на стеку, а не на купі - посилання на цей об'єкт. Що стосується продуктивності, то вам доведеться чіткіше зрозуміти, про яке "це" ви говорите.
Даніель К. Собрал

Гаразд, з вашою допомогою та деякою інформацією ( (x::xs).drop(1)це точно xs, а не "копія" xs) звідси посилання, яке я міг зрозуміти. tnx!

"Цей код все ще ефективний" - чи не так? Оскільки qrє незмінною чергою, кожен раз, коли qr.dequeueвикликається вираз , він робить new Queue(див. < Github.com/scala/scala/blob/2.13.x/src/library/scala/collection/… ).
Оуен

@Owen Так, але зауважте, що це дрібний предмет. Код як і раніше O (n), чи є змінним, якщо ви копіюєте чергу, чи незмінним.
Даніель К. Собрал

61

valє остаточним, тобто не може бути встановлений. Думай finalв яві.


3
Але якщо я правильно розумію (не експерт Scala), val змінні незмінні, але об'єкти, на які вони посилаються, не повинні бути. За посиланням Стефана, розміщеним: "Тут посилання на імена неможливо змінити, щоб вказати на інший масив, але сам масив можна змінити. Іншими словами, вміст / елементи масиву можуть бути змінені." Так це, як finalпрацює на Java.
Даніель Приден

3
Саме тому я і розмістив його таким, яким є. Я можу зателефонувати +=на мутабельну хешмап, визначену як valпросто чудову - я вважаю, як саме вона finalпрацює в Java
Jackson Davis

Ак, я думав, що вбудовані типи шкали можуть зробити краще, ніж просто дозволяти повторне призначення. Мені потрібно перевірити факти.
Стефан Кендалл

Я плутав незмінні типи послідовності Scala із загальним поняттям. Функціональне програмування мене все обернуло.
Стефан Кендалл

У вашій відповіді я додав і видалив фіктивний персонаж, щоб я міг дати вам нагороду.
Стефан Кендалл


20

Різниця полягає в тому, що varможна перепризначити, тоді як valне можна. Змінюваність чи інше того, що фактично призначено, є побічною проблемою:

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.

При цьому:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

І отже:

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

Якщо ви будуєте структуру даних, і всі її поля є vals, то ця структура даних є незмінною, оскільки її стан не може змінюватися.


1
Це справедливо лише в тому випадку, якщо класи цих полів також незмінні.
Даніель К. Собрал

Так - я збирався це поставити, але я подумав, що це може бути крок занадто далеко! Я б також сказав, що це спірний момент; з одного погляду (хоч і не функціонального) його стан не змінюється, навіть якщо стан його держави.
oxbow_lakes

Чому досі так важко створити незмінний об’єкт мовою JVM? Крім того, чому Скала не зробила об'єкти незмінними за замовчуванням?
Дерек Махар

19

valозначає незмінний і varозначає змінний.

Повна дискусія.


1
Це просто неправда. Пов'язана стаття надає змінний масив і називає його незмінним. Немає серйозних джерел.
Клаус Шульц

Справді, неправда. Спробуйте, що val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Гійом

13

Думаючи з точки зору C ++,

val x: T

є аналогом постійного вказівника на непостійні дані

T* const x;

поки

var x: T 

є аналогом непостійного покажчика непостійним даним

T* x;

Улюблений valпонадvar підвищує незмінність кодової бази, що може полегшити її правильність, одночасність та зрозумілість.

Для розуміння сенсу постійного вказівника на незмінні дані розглянемо наступний фрагмент Scala:

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

Тут "покажчик" val m є постійним, тому ми не можемо повторно призначити його, щоб вказати на щось інше, як так

m = n // error: reassignment to val

однак ми можемо дійсно змінити самі постійні дані, які mвказують на подібне

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)

1
Я думаю, що Скала не прийняв незмінності до свого остаточного висновку: постійний покажчик та постійні дані. Скала пропустила можливість зробити об’єкти незмінними за замовчуванням. Отже, Scala не має того самого значення поняття, що і Haskell.
Дерек Махар

@DerekMahar ви маєте рацію, але об’єкт може представляти себе як непорушний, при цьому все ще використовуючи мутаційність у своїй реалізації, наприклад, з міркувань продуктивності. Як компілятор міг би розділяти між справжньою та незмінною внутрішньою мінливістю?
francoisr

8

"val означає незмінний, а var означає змінний."

Якщо перефразовувати, "val означає значення, а var означає змінну".

Відмінність, яка виявляється надзвичайно важливою в обчислювальних технологіях (адже ці два поняття визначають саму суть того, в чому полягає програмування), і що OO вдалося розмити майже повністю, тому що в ОО єдиною аксіомою є те, що "все є об’єкт ". І це, як наслідок, багато програмістів в наші дні, як правило, не розуміють / оцінюють / не визнають, тому що їм промили мозок виключно "мислячи шлях OO". Часто ведучи до змінних / змінних об'єктів, що використовуються, як і скрізь , коли значення / незмінні об'єкти могли / часто були б кращими.


З цієї причини, наприклад, я віддаю перевагу Haskell над Java.
Дерек Махар

6

val означає незмінний, а var означає змінний

ви можете мислити valяк finalключовий світ мови програмування java або світ ключових мов c ++ const


1

Valозначає його остаточний , не може бути перепризначений

Тоді як Varможна перепризначити пізніше .


1
Чим ця відповідь відрізняється від 12 поданих відповідей?
jwvh

0

Це так просто, як його називають.

var означає, що вона може змінюватися

val означає незмінний


0

Значення Val - введені константи зберігання. Після створення його значення не можна перепризначити. нове значення можна визначити за допомогою ключового слова val.

напр. val x: Int = 5

Тут тип є необов’язковим, оскільки scala може визначити його із призначеного значення.

Var - змінні - це типові одиниці зберігання, яким можна знову присвоїти значення, доки зарезервовано простір пам'яті.

напр. var x: Int = 5

Дані, що зберігаються в обох блоках зберігання, автоматично розподіляються JVM після того, як вони більше не потрібні.

У масштабі масштабів перевагу надають перевагам змінним через стабільність, що приводить до коду, особливо у одночасному та багатопотоковому коді.


0

Хоча багато хто вже відповіли на різницю між Вал та вар . Але слід зазначити, що val не зовсім як фінал ключове слово.

Ми можемо змінити значення val за допомогою рекурсії, але ми ніколи не можемо змінити значення остаточного. Фінал більш постійний, ніж Вал.

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

Параметри методу за замовчуванням знаходяться за вартістю і при кожному значенні виклику змінюється.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.