Recherche…


Remarques

Comme val sont sémantiquement statiques, ils sont initialisés "sur place" partout où ils apparaissent dans le code. Cela peut produire un comportement surprenant et indésirable lorsqu'il est utilisé dans des classes et des traits abstraits.

Par exemple, disons que nous aimerions créer un trait appelé PlusOne qui définit une opération d'incrémentation sur un Int PlusOne . Puisque les s Int sont immuables, la valeur plus un est connue à l'initialisation et ne sera jamais modifiée par la suite, donc sémantiquement, c'est un val . Cependant, sa définition de cette manière produira un résultat inattendu.

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

Peu importe quelle valeur i vous construis IntWrapper avec, en appelant .incr sur l'objet retourné retournera toujours 1. En effet , le val incr est initialisé dans le trait, avant la classe d' extension, et à ce moment - là , i ne dispose que la valeur par défaut 0 . (Dans d'autres conditions, il peut être rempli avec Nil , null ou une valeur par défaut similaire.)

La règle générale est donc d'éviter d'utiliser val sur toute valeur qui dépend d'un champ abstrait. Au lieu de cela, utilisez lazy val , qui n'évalue pas jusqu'à ce qu'il soit nécessaire, ou def , qui évalue chaque fois qu'il est appelé. Notez cependant que si le lazy val est forcé à évaluer par un val avant la fin de l'initialisation, la même erreur se produira.

Un violon (écrit en Scala-Js, mais le même comportement s’applique) peut être trouvé ici.

Var, Val et Def

var

Un var est une variable de référence, similaire aux variables dans des langages comme Java. Différents objets peuvent être librement assignés à une var , tant que l'objet donné a le même type que celui avec lequel la var été déclarée:

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

Remarquez dans l'exemple ci-dessus que le type du var été déduit par le compilateur en fonction de la première affectation de valeur.

val

Un val est une référence constante. Ainsi, un nouvel objet ne peut pas être attribué à un val qui a déjà été attribué.

scala> val y = 1
y: Int = 1

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

Cependant, l'objet d' un val pointe est pas constante. Cet objet peut être modifié:

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)

déf

Un def définit une méthode. Une méthode ne peut pas être réaffectée à.

scala> def z = 1
z: Int

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

Dans les exemples ci-dessus, val y et def z renvoient la même valeur. Cependant, un def est évalué quand il est appelé , alors qu'un val ou var est évalué quand il est assigné . Cela peut entraîner des comportements différents lorsque la définition a des effets secondaires:

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

Les fonctions

Les fonctions étant des valeurs, elles peuvent être affectées à val / var / def s. Tout le reste fonctionne de la même manière que ci-dessus:

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 est une fonctionnalité de langage dans laquelle l'initialisation d'un val est retardée jusqu'à ce qu'il soit accédé pour la première fois. Après cela, il agit comme un val régulier.

Pour l'utiliser, ajoutez le mot-clé lazy avant val . Par exemple, en utilisant le 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

Cet exemple illustre l'ordre d'exécution. Lorsque le lazy val est déclaré, tout ce qui est enregistré dans la valeur foo est un appel de fonction paresseux qui n'a pas encore été évalué. Lorsque le régulier val est réglé, nous voyons le println appel et exécuter la valeur est affectée à la bar . Lorsque nous évaluons foo la première fois que println s'exécute - mais pas quand il est évalué la deuxième fois. De même, lorsque bar est évalué, nous ne voyons pas println execute - seulement quand il est déclaré.

Quand utiliser 'paresseux'

  1. L'initialisation est coûteuse en calculs et l'utilisation de val est rare.

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

    tiresomeValue prend beaucoup de temps à calculer, et il n'est pas toujours utilisé. Faire un lazy val enregistre des calculs inutiles.

  2. Résoudre les dépendances cycliques

    Regardons un exemple avec deux objets qui doivent être déclarés en même temps lors de l'instanciation:

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

    Sans le mot-clé lazy , les objets respectifs ne peuvent pas être membres d'un objet. L'exécution d'un tel programme entraînerait une java.lang.NullPointerException . En utilisant lazy , la référence peut être assignée avant son initialisation, sans crainte d'avoir une valeur non initialisée.

Surcharge surcharge

Vous pouvez surcharger un def si la signature est différente:

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

Cela fonctionne de la même façon que ce soit à l'intérieur des classes, des traits, des objets ou non.

Paramètres nommés

Lors de l'appel d'un def , les paramètres peuvent être attribués explicitement par leur nom. Cela signifie qu'ils n'ont pas besoin d'être correctement commandés. Par exemple, définissez printUs() comme:

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

Maintenant, il peut être appelé de cette façon (entre autres):

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

Cela se traduit par one, two, three étant imprimés dans tous les cas.

Si tous les arguments ne sont pas nommés, les premiers arguments sont comparés par ordre. Aucun argument positionnel (non nommé) ne peut suivre un nommé:

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow