В чем разница между a var
и val
определением в Scala и почему язык нуждается в обоих? Почему вы выбрали бы val
более var
и наоборот?
В чем разница между a var
и val
определением в Scala и почему язык нуждается в обоих? Почему вы выбрали бы val
более var
и наоборот?
Ответы:
Как говорили многие другие, объект, назначенный val
элементу, не может быть заменен, а объект, назначенный элементу var
can. Однако у указанного объекта может быть изменено его внутреннее состояние. Например:
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 есть и свои сильные стороны. Посмотрим, что будет в будущем.
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/… ).
val
является окончательным, то есть не может быть установлен. Думай final
в яве.
val
переменные являются неизменяемыми, но объекты, на которые они ссылаются, необязательно должны быть. По ссылке Стефан написал: «Здесь ссылка на имена не может быть изменена, чтобы указывать на другой массив, но сам массив может быть изменен. Другими словами, содержимое / элементы массива могут быть изменены». Так что это похоже на то, как final
работает в Java.
+=
изменяемый hashmap, определенный как очень val
хороший - я верю, что именно так final
работает в Java
Проще говоря:
вар = переменная iable
val = v ariable + fin al
Разница в том, что а 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.
Если вы строите структуру данных и все ее поля имеют значение val
s, то эта структура данных является неизменной, поскольку ее состояние не может измениться.
val
означает неизменный и var
означает изменчивый.
Мышление с точки зрения 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)
«val означает неизменный, а var означает изменяемый».
Перефразируя, «val означает значение, а var означает переменную».
Различие, которое оказывается чрезвычайно важным в вычислениях (потому что эти два понятия определяют саму суть всего, что связано с программированием), и что ОО удалось почти полностью размывать, потому что в ОО единственная аксиома заключается в том, что «все является объект». И как следствие, многие программисты в наши дни склонны не понимать / ценить / распознавать, потому что им «промыли мозги» исключительно для «осмысления ОО-пути». Часто это приводит к тому, что переменные / изменяемые объекты используются, как и везде , когда значения / неизменяемые объекты могут / часто были бы лучше.
val означает неизменяемый, а var означает изменяемый
Вы можете думать val
как final
ключевой мир языка программирования Java или const
ключевой мир языка C ++ 。
Val
значит его окончательный , нельзя переназначен
Принимая во внимание, что Var
может быть переназначен позже .
Это так просто, как назвать.
var означает, что может меняться
val означает неизменный
Val - значения являются типизированными константами хранения. После создания его значение не может быть переназначено. новое значение можно определить с помощью ключевого слова val.
например. val x: Int = 5
Здесь type является необязательным, поскольку scala может вывести его из заданного значения.
Переменные - это типизированные единицы хранения, которым можно снова присвоить значения, если зарезервировано место в памяти.
например. var x: Int = 5
Данные, хранящиеся в обеих единицах хранения, автоматически удаляются JVM, когда они больше не нужны.
В scala значения предпочтительнее переменных из-за стабильности, что приводит к коду, особенно в параллельном и многопоточном коде.
Хотя многие уже ответили на разницу между Val и var . Но следует отметить, что val не совсем как final ключевое слово.
Мы можем изменить значение val с помощью рекурсии, но мы никогда не сможем изменить значение final. Финал более постоянен, чем Вал.
def factorial(num: Int): Int = {
if(num == 0) 1
else factorial(num - 1) * num
}
Параметры метода по умолчанию - val, и при каждом вызове значение изменяется.
var qr = q
копияq
?