Поиск…


замечания

Поскольку 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 - только при его объявлении.

Когда использовать «ленивый»

  1. Инициализация является дорогостоящим вычислительным методом, а использование val является редкостью.

    lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum}
    if (scala.util.Random.nextInt > 1000) {
      println(tiresomeValue)
    }
    

    tiresomeValue занимает много времени, и она не всегда используется. Делать это lazy val спасает ненужные вычисления.

  2. Разрешение циклических зависимостей

    Давайте рассмотрим пример с двумя объектами, которые должны быть объявлены одновременно во время создания экземпляра:

    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'


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow