Buscar..


Observaciones

Como los val son semánticamente estáticos, se inicializan "en el lugar" donde aparecen en el código. Esto puede producir un comportamiento sorprendente e indeseable cuando se usa en clases abstractas y rasgos.

Por ejemplo, digamos que nos gustaría hacer un rasgo llamado PlusOne que defina una operación de incremento en un Int envuelto. Dado que los Int s son inmutables, el valor más uno se conoce en la inicialización y nunca se cambiará después, por lo que semánticamente es un valor val . Sin embargo, definirlo de esta manera producirá un resultado inesperado.

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

No importa cuál es el valor i se construye IntWrapper con, llamando .incr en el objeto devuelto siempre devuelve 1. Esto es porque el val incr es inicializado en el rasgo, antes de la clase que se extiende, y en ese momento i sólo tiene el valor por defecto de 0 . (En otras condiciones, puede completarse con Nil , null o un valor predeterminado similar).

La regla general, entonces, es evitar usar val en cualquier valor que dependa de un campo abstracto. En su lugar, use lazy val , que no evalúa hasta que se necesita, o def , que evalúa cada vez que se llama. Sin embargo, tenga en cuenta que si el valor de lazy val es forzado a evaluar por un val antes de que se complete la inicialización, ocurrirá el mismo error.

Un violín (escrito en Scala-Js, pero se aplica el mismo comportamiento) se puede encontrar aquí.

Var, Val y Def

var

Una var es una variable de referencia, similar a las variables en lenguajes como Java. Se pueden asignar diferentes objetos a una var libremente, siempre que el objeto dado tenga el mismo tipo con el que se declaró la 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"
       ^

Observe en el ejemplo anterior el tipo de la var fue inferida por el compilador dada la primera asignación de valor.

val

Un val es una referencia constante. Por lo tanto, no se puede asignar un nuevo objeto a un val que ya se ha asignado.

scala> val y = 1
y: Int = 1

scala> y = 2
<console>:12: error: reassignment to val
       y = 2
         ^

Sin embargo, el objeto al que apunta un val no es constante. Ese objeto puede ser modificado:

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

Una def define un método. Un método no puede ser reasignado a

scala> def z = 1
z: Int

scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
       z = 2
       ^

En los ejemplos anteriores, val y y def z devuelven el mismo valor. Sin embargo, una def se evalúa cuando se llama , mientras que una val o var se evalúa cuando se asigna . Esto puede resultar en un comportamiento diferente cuando la definición tiene efectos secundarios:

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

Funciones

Como las funciones son valores, se pueden asignar a val / var / def s. Todo lo demás funciona de la misma manera que arriba:

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

Perezoso val

lazy val es una función de lenguaje en la que la inicialización de un val se retrasa hasta que se accede por primera vez. Después de ese punto, actúa como un val normal.

Para usarlo agregue la palabra clave lazy antes de val . Por ejemplo, usando el 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

Este ejemplo demuestra el orden de ejecución. Cuando el lazy val se declara, todo lo que se guarda en el foo valor es una llamada a la función vago que no ha sido evaluado. Cuando el normal val se establece, vemos el println llamada de ejecutar y se le asigna el valor a bar . Cuando evaluamos foo la primera vez, vemos println ejecuta println , pero no cuando se evalúa la segunda vez. De manera similar, cuando se evalúa la bar , no vemos que se ejecute println , solo cuando se declara.

Cuando usar 'perezoso'

  1. La inicialización es computacionalmente costosa y el uso de val es raro.

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

    tiresomeValue tarda mucho tiempo en calcularse, y no siempre se usa. Si se convierte en un valor lazy val ahorra cómputo innecesario.

  2. Resolución de dependencias cíclicas.

    Veamos un ejemplo con dos objetos que deben declararse al mismo tiempo durante la creación de instancias:

    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")
    }
    

    Sin la palabra clave lazy , los objetos respectivos no pueden ser miembros de un objeto. La ejecución de dicho programa daría lugar a una java.lang.NullPointerException . Mediante el uso lazy , la referencia se puede asignar antes de que se inicialice, sin temor a tener un valor sin inicializar.

Sobrecarga def

Puede sobrecargar una def si la firma es diferente:

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'"

Esto funciona de la misma manera ya sea dentro de clases, rasgos, objetos o no.

Parámetros con nombre

Al invocar una def , los parámetros pueden asignarse explícitamente por nombre. Si lo hace, significa que no necesitan ser ordenados correctamente. Por ejemplo, defina printUs() como:

// print out the three arguments in order.
def printUs(one: String, two: String, three: String) = 
   println(s"$one, $two, $three")

Ahora se puede llamar de estas maneras (entre otras):

printUs("one", "two", "three") 
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two") 

Esto hace que se impriman one, two, three en todos los casos.

Si no se nombran todos los argumentos, los primeros argumentos se comparan por orden. Ningún argumento posicional (sin nombre) puede seguir a uno nombrado:

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
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow