В чем разница между определениями var и val в Scala?

258

В чем разница между определениями var и val в Scala и почему нужен язык для обоих? Почему вы выбрали val над a var и наоборот?

Теги:

11 ответов

280
Лучший ответ

Как и многие другие, объект, назначенный 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 решил, чтобы программист сделал это, если программист счел это лучшим решением. Другие языки решили сделать такой код сложным. Цена Scala (и любой язык с широко распространенной изменчивостью) платит, так это то, что у компилятора не так много возможностей для оптимизации кода, как в противном случае. Ответ Java на это - оптимизация кода на основе профиля времени выполнения. Мы могли бы продолжать и обсуждать все плюсы и минусы с каждой стороны.

Лично я думаю, что Scala покажет правильный баланс. Это далеко не идеальный вариант. Я думаю, что Clojure и Haskell имеют очень интересные понятия, не принятые Scala, но Scala имеет свои сильные стороны. Мы увидим, что происходит в будущем.

  • 0
    Немного поздно, но ... Делает ли var qr = q копию q ?
  • 0
    Как это влияет на производительность (время процессора)?
Показать ещё 2 комментария
47

val является окончательным, то есть не может быть задано. Подумайте final в java.

  • 3
    Но если я правильно понимаю (не эксперт по Scala), переменные val являются неизменяемыми, но объекты, на которые они ссылаются, не обязательно должны быть. По ссылке Стефан написал: «Здесь ссылка на имена не может быть изменена, чтобы указывать на другой массив, но сам массив может быть изменен. Другими словами, содержимое / элементы массива могут быть изменены». Так что это похоже на то, как final работает в Java.
  • 3
    Именно поэтому я разместил это как есть. Я могу вызвать += на mutabled hashmap, определенном как val просто отлично - я верю, что именно так работает final в java
Показать ещё 4 комментария
34

Простыми словами:

var= var iable

val= va reiable + fin al

19

Разница в том, что a var может быть повторно назначено, тогда как a 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.

Если вы создаете структуру данных, и все ее поля val s, то эта структура данных поэтому неизменяема, так как ее состояние не может измениться.

  • 1
    Это верно только в том случае, если классы этих полей также неизменны.
  • 0
    Да - я собирался вставить это, но я думал, что это может быть слишком далеко! Это также спорный момент, я бы сказал; с одной точки зрения (хотя и не функциональной) его состояние не меняется, даже если состояние его состояния меняется.
Показать ещё 1 комментарий
19

val означает неизменяемый и var означает изменчивый.

Полное обсуждение.

  • 1
    Это просто неправда. Связанная статья дает изменяемый массив и называет его неизменным. Нет серьезного источника.
  • 0
    На самом деле это не так. Попробуйте, что val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
11

Думая о C++,

val x: T

аналогичен постоянному указателю на непостоянные данные

T* const x;

while

var x: T 

аналогичен непостоянному указателю на непостоянные данные

T* x;

Приоритет val над var увеличивает неизменность кодовой базы, которая может облегчить ее правильность, concurrency и понятность.

  • 1
    Я думаю, что Scala не привел неизменность к своему окончательному выводу: постоянный указатель и постоянные данные. Scala упустила возможность сделать объекты неизменяемыми по умолчанию. Следовательно, у Scala нет такого понятия ценности, как у Haskell.
  • 0
    @DerekMahar вы правы, но объект может представлять себя как совершенно неизменный, при этом все еще используя изменчивость в своей реализации, например, из соображений производительности. Как компилятор сможет разделить истинную изменчивость и изменчивость только для внутреннего использования?
8

"val означает неизменяемый и var означает mutable."

Чтобы перефразировать, значение val означает значение, а var означает переменную ".

Различие, которое имеет чрезвычайно важное значение при вычислении (поскольку эти два понятия определяют саму суть программирования) и что OO удалось полностью размыть, поскольку в OO единственная аксиома заключается в том, что "все это объект". И, как следствие, многие программисты в наши дни, как правило, не понимают/ценят/не признают, потому что им "промывают мозги" исключительно "мышлением OO". Часто приводя к переменным/изменяемым объектам, которые используются как везде, когда значения/неизменяемые объекты могли/часто были лучше.

  • 0
    По этой причине я предпочитаю, например, Haskell, а не Java.
6

val означает неизменяемое, а var означает mutable

вы можете думать val как язык Java-программирования final ключевой мир или язык С++ языка const.

2

A val похож на конечную переменную в Java. После инициализации val никогда не может быть переназначена.

A var, напротив, похож на не конечную переменную в Java. var можно переназначить на протяжении всего срока его службы.

1

Это так просто, как это называется.

var означает, что он может меняться

val означает неизменное

0

Значения Val - это типизированные константы хранения. После создания его значение не может быть повторно назначено. новое значение может быть определено с помощью ключевого слова val.

например. val x: Int = 5

Здесь тип необязателен, поскольку scala может вывести его из назначенного значения.

Var - переменные представляют собой типизированные единицы хранения, которые могут быть назначены значения снова, пока зарезервировано пространство памяти.

например. var x: Int = 5

Данные, хранящиеся в обоих блоках памяти, автоматически де-распределяются JVM, если они больше не нужны.

В scala значения предпочтительны по сравнению с переменными из-за стабильности, которые приводят к коду, в частности, в параллельном и многопоточном коде.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню