Scala Language
Var, Val и Def
Поиск…
замечания
Поскольку val
семантически статичны, они инициализируются «на месте», где бы они ни появлялись в коде. Это может вызвать неожиданное и нежелательное поведение при использовании в абстрактных классах и чертах.
Например, предположим, что мы хотели бы сделать признак, называемый PlusOne
который определяет операцию приращения на обернутом Int
. Поскольку Int
s неизменяемы, значение плюс один известно при инициализации и никогда не будет изменено впоследствии, поэтому семантически это значение val
. Однако определение этого способа приведет к неожиданному результату.
trait PlusOne {
val i:Int
val incr = i + 1
}
class IntWrapper(val i: Int) extends PlusOne
Независимо от того, какое значение i
IntWrapper
, вызов .incr
в возвращаемом объекте всегда будет возвращаться 1. Это происходит потому, что val incr
инициализируется в признаке , перед расширяющим классом, и в это время у i
есть только значение по умолчанию 0
. (В других условиях он может быть заполнен Nil
, null
или аналогичным значением по умолчанию).
Таким образом, общее правило состоит в том, чтобы избежать использования val
для любого значения, которое зависит от абстрактного поля. Вместо этого используйте lazy val
, который не оценивается, пока он не понадобится, или def
, который оценивает каждый раз, когда он вызывается. Обратите внимание, однако, что если lazy val
вынужден оценивать по val
до завершения инициализации, произойдет такая же ошибка.
Здесь можно найти скрипку (написанную в Scala-Js, но такое же поведение) .
Var, Val и Def
вар
Параметр var
является ссылочной переменной, подобной переменным в таких языках, как Java. Различные объекты могут быть свободно назначены для var
, если данный объект имеет тот же тип, что и var
:
scala> var x = 1
x: Int = 1
scala> x = 2
x: Int = 2
scala> x = "foo bar"
<console>:12: error: type mismatch;
found : String("foo bar")
required: Int
x = "foo bar"
^
Обратите внимание, что в примере выше тип var
был выведен компилятором при первом присвоении значения.
вал
val
является постоянной ссылкой. Таким образом, новый объект не может быть назначен val
, которое уже было назначено.
scala> val y = 1
y: Int = 1
scala> y = 2
<console>:12: error: reassignment to val
y = 2
^
Однако объект, на который указывает val
, не является постоянным. Этот объект может быть изменен:
scala> val arr = new Array[Int](2)
arr: Array[Int] = Array(0, 0)
scala> arr(0) = 1
scala> arr
res1: Array[Int] = Array(1, 0)
Защита
def
определяет метод. Метод не может быть повторно назначен.
scala> def z = 1
z: Int
scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
z = 2
^
В приведенных выше примерах val y
и def z
возвращают одно и то же значение. Тем не менее, def
оценивается, когда он вызывается , тогда как val
или var
оценивается, когда он назначен . Это может привести к различному поведению, когда определение имеет побочные эффекты:
scala> val a = {println("Hi"); 1}
Hi
a: Int = 1
scala> def b = {println("Hi"); 1}
b: Int
scala> a + 1
res2: Int = 2
scala> b + 1
Hi
res3: Int = 2
функции
Поскольку функции являются значениями, они могут быть назначены val
/ var
/ def
s. Все остальное работает так же, как описано выше:
scala> val x = (x: Int) => s"value=$x"
x: Int => String = <function1>
scala> var y = (x: Int) => s"value=$x"
y: Int => String = <function1>
scala> def z = (x: Int) => s"value=$x"
z: Int => String
scala> x(1)
res0: String = value=1
scala> y(2)
res1: String = value=2
scala> z(3)
res2: String = value=3
Lazy val
lazy val
- это языковая функция, при которой инициализация val
задерживается до тех пор, пока она не будет доступна в первый раз. После этого момента он действует как обычный val
.
Чтобы использовать его, добавьте lazy
ключевое слово до val
. Например, используя REPL:
scala> lazy val foo = {
| println("Initializing")
| "my foo value"
| }
foo: String = <lazy>
scala> val bar = {
| println("Initializing bar")
| "my bar value"
| }
Initializing bar
bar: String = my bar value
scala> foo
Initializing
res3: String = my foo value
scala> bar
res4: String = my bar value
scala> foo
res5: String = my foo value
Этот пример демонстрирует порядок выполнения. Когда объявляется lazy val
, все, что сохраняется в значение foo
- это ленивый вызов функции, который еще не был оценен. Когда установлен правильный val
, мы видим, что вызов println
выполняется, и значение присваивается bar
. Когда мы evalute foo
в первый раз видим, что println
выполняется, но не тогда, когда он оценивается во второй раз. Аналогично, когда bar
оценивается, мы не видим println
execute - только при его объявлении.
Когда использовать «ленивый»
Инициализация является дорогостоящим вычислительным методом, а использование
val
является редкостью.lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum} if (scala.util.Random.nextInt > 1000) { println(tiresomeValue) }
tiresomeValue
занимает много времени, и она не всегда используется. Делать этоlazy val
спасает ненужные вычисления.Разрешение циклических зависимостей
Давайте рассмотрим пример с двумя объектами, которые должны быть объявлены одновременно во время создания экземпляра:
object comicBook { def main(args:Array[String]): Unit = { gotham.hero.talk() gotham.villain.talk() } } class Superhero(val name: String) { lazy val toLockUp = gotham.villain def talk(): Unit = { println(s"I won't let you win ${toLockUp.name}!") } } class Supervillain(val name: String) { lazy val toKill = gotham.hero def talk(): Unit = { println(s"Let me loosen up Gotham a little bit ${toKill.name}!") } } object gotham { val hero: Superhero = new Superhero("Batman") val villain: Supervillain = new Supervillain("Joker") }
Без ключевого слова
lazy
соответствующие объекты не могут быть членами объекта. Выполнение такой программы приведет кjava.lang.NullPointerException
. Используяlazy
, ссылка может быть назначена до ее инициализации, не опасаясь иметь неинициализированное значение.
Перегрузка Def
Вы можете перегрузить def
если подпись отличается:
def printValue(x: Int) {
println(s"My value is an integer equal to $x")
}
def printValue(x: String) {
println(s"My value is a string equal to '$x'")
}
printValue(1) // prints "My value is an integer equal to 1"
printValue("1") // prints "My value is a string equal to '1'"
Это работает так же, как внутри классов, черт, объектов или нет.
Именованные параметры
При вызове def
параметры могут быть назначены явно по имени. Это означает, что они не должны быть правильно заказаны. Например, определите printUs()
как:
// print out the three arguments in order.
def printUs(one: String, two: String, three: String) =
println(s"$one, $two, $three")
Теперь это можно назвать таким образом (среди прочих):
printUs("one", "two", "three")
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two")
Это приводит к one, two, three
что во всех случаях печатается one, two, three
.
Если не все аргументы названы, первые аргументы сопоставляются по порядку. Никакой позиционный (неименованный) аргумент не может соответствовать названному:
printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'