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


301

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

Ответы:


331

Как говорили многие другие, объект, назначенный valэлементу, не может быть заменен, а объект, назначенный элементу varcan. Однако у указанного объекта может быть изменено его внутреннее состояние. Например:

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 на это - оптимизация кода на основе профиля времени выполнения. Мы могли бы говорить о плюсах и минусах каждой стороны.

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


Немного поздно, но ... Делает ли 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/… ).
Оуэн

@ Да, но обратите внимание, что это мелкий объект. Код по-прежнему O (n), может ли быть изменяемым, если вы копируете очередь, или неизменным.
Даниэль С. Собрал,

61

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


3
Но если я правильно понимаю (не эксперт по Scala), val переменные являются неизменяемыми, но объекты, на которые они ссылаются, необязательно должны быть. По ссылке Стефан написал: «Здесь ссылка на имена не может быть изменена, чтобы указывать на другой массив, но сам массив может быть изменен. Другими словами, содержимое / элементы массива могут быть изменены». Так что это похоже на то, как finalработает в Java.
Даниэль Приден

3
Именно поэтому я разместил это как есть. Я могу вызвать +=изменяемый hashmap, определенный как очень valхороший - я верю, что именно так finalработает в Java
Джексон Дэвис

Я подумал, что встроенные типы scala могут работать лучше, чем просто переопределение. Мне нужно проверить факт.
Стефан Кендалл

Я путал неизменные типы последовательностей в 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? Кроме того, почему Scala не делает объекты неизменяемыми по умолчанию?
Дерек Махар

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 не привел неизменность к своему окончательному выводу: постоянный указатель и постоянные данные. Scala упустила возможность сделать объекты неизменяемыми по умолчанию. Следовательно, у Scala нет такого понятия ценности, как у Haskell.
Дерек Махар

@DerekMahar вы правы, но объект может представлять себя как совершенно неизменный, при этом все еще используя изменчивость в своей реализации, например, из соображений производительности. Как компилятор сможет разделить истинную изменчивость и изменчивость только для внутреннего использования?
francoisr

8

«val означает неизменный, а var означает изменяемый».

Перефразируя, «val означает значение, а var означает переменную».

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


По этой причине я предпочитаю, например, Haskell, а не Java.
Дерек Махар

6

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

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


1

Valзначит его окончательный , нельзя переназначен

Принимая во внимание, что Varможет быть переназначен позже .


1
Чем этот ответ отличается от 12 уже представленных ответов?
jwvh

0

Это так просто, как назвать.

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

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


0

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

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

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

Переменные - это типизированные единицы хранения, которым можно снова присвоить значения, если зарезервировано место в памяти.

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

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

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


0

Хотя многие уже ответили на разницу между Val и var . Но следует отметить, что val не совсем как final ключевое слово.

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

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

Параметры метода по умолчанию - val, и при каждом вызове значение изменяется.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.