Scala Language
Var, Val et Def
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'
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 unlazy val
enregistre des calculs inutiles.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 unejava.lang.NullPointerException
. En utilisantlazy
, 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'