Ai-je raison de comprendre que
def
est évalué à chaque accès
lazy val
est évalué une fois accessible
val
est évalué une fois qu'il entre dans la portée d'exécution?
Oui, mais pour le 3ème je dirais "quand cette instruction sera exécutée", parce que, par exemple:
def foo() {
new {
val a: Any = sys.error("b is " + b)
val b: Any = sys.error("a is " + a)
}
}
Cela donne "b is null"
. b
n'est jamais évalué et son erreur n'est jamais levée. Mais il est à portée dès que le contrôle entre dans le bloc.
Oui, mais il y a une astuce intéressante: si vous avez une valeur paresseuse, et lors de la première évaluation, il obtiendra une exception, la prochaine fois que vous essayerez d'accéder, elle essaiera de se réévaluer.
Voici un exemple:
scala> import io.Source
import io.Source
scala> class Test {
| lazy val foo = Source.fromFile("./bar.txt").getLines
| }
defined class Test
scala> val baz = new Test
baz: Test = Test@ea5d87
//right now there is no bar.txt
scala> baz.foo
Java.io.FileNotFoundException: ./bar.txt (No such file or directory)
at Java.io.FileInputStream.open(Native Method)
at Java.io.FileInputStream.<init>(FileInputStream.Java:137)
...
// now I've created empty file named bar.txt
// class instance is the same
scala> baz.foo
res2: Iterator[String] = empty iterator
Je voudrais expliquer les différences à travers l'exemple que j'ai exécuté dans REPL.Je crois que cet exemple simple est plus facile à saisir et explique les différences conceptuelles.
Ici, je crée un résultat val1, un résultat val paresseux2 et un résultat def3 qui ont chacun un type String.
NE). val
scala> val result1 = {println("hello val"); "returns val"}
hello val
result1: String = returns val
Ici, println est exécuté car la valeur de result1 a été calculée ici. Ainsi, result1 se référera toujours à sa valeur, c'est-à-dire "renvoie val".
scala> result1
res0: String = returns val
Donc, maintenant, vous pouvez voir que result1 fait maintenant référence à sa valeur. Notez que l'instruction println n'est pas exécutée ici car la valeur de result1 a déjà été calculée lors de sa première exécution. Ainsi, désormais, result1 retournera toujours la même valeur et l'instruction println ne sera plus jamais exécutée car le calcul pour obtenir la valeur de result1 a déjà été effectué.
B). paresseux val
scala> lazy val result2 = {println("hello lazy val"); "returns lazy val"}
result2: String = <lazy>
Comme nous pouvons le voir ici, l'instruction println n'est pas exécutée ici et ni la valeur n'a été calculée. Telle est la nature de la paresse.
Maintenant, quand je me réfère au result2 pour la première fois, l'instruction println sera exécutée et la valeur sera calculée et affectée.
scala> result2
hello lazy val
res1: String = returns lazy val
Maintenant, quand je fais à nouveau référence à result2, cette fois-ci, nous ne verrons que la valeur qu'il contient et l'instruction println ne sera pas exécutée. Désormais, result2 se comportera simplement comme un val et retournera sa valeur en cache tout le temps.
scala> result2
res2: String = returns lazy val
C). def
En cas de def, le résultat devra être calculé chaque fois que result3 est appelé. C'est aussi la raison principale pour laquelle nous définissons les méthodes comme def dans scala parce que les méthodes doivent calculer et retourner une valeur chaque fois qu'elle est appelée dans le programme.
scala> def result3 = {println("hello def"); "returns def"}
result3: String
scala> result3
hello def
res3: String = returns def
scala> result3
hello def
res4: String = returns def
Une bonne raison de choisir def
plutôt que val
, en particulier dans les classes abstraites (ou dans les traits utilisés pour imiter les interfaces Java), est que vous pouvez remplacer un def
avec un val
dans les sous-classes, mais pas l'inverse.
En ce qui concerne lazy
, je peux voir deux choses que l'on devrait avoir à l'esprit. La première est que lazy
introduit une surcharge d'exécution, mais je suppose que vous auriez besoin de comparer votre situation spécifique pour savoir si cela a réellement un impact significatif sur les performances d'exécution. L'autre problème avec lazy
est qu'il peut retarder la levée d'une exception, ce qui pourrait rendre plus difficile de raisonner sur votre programme, car l'exception n'est pas levée d'avance mais uniquement lors de la première utilisation.
Vous avez raison. Pour des preuves de la spécification :
Dans "3.3.1 Types de méthodes" (pour def
):
Expressions de nom de méthodes sans paramètre qui sont réévaluées chaque fois que le nom de méthode sans paramètres est référencé.
Extrait de "4.1 Déclarations et définitions de valeurs":
Une définition de valeur
val x : T = e
définitx
comme un nom de la valeur qui résulte de l'évaluation dee
.Une définition de valeur paresseuse évalue son côté droit
e
la première fois que la valeur est accédée.
def
définit une méthode. Lorsque vous appelez la méthode, celle-ci s'exécute.
val
définit une valeur (une variable immuable). L'expression d'affectation est évaluée lorsque la valeur est initialisée.
lazy val
définit une valeur avec une initialisation retardée. Il sera initialisé lors de sa première utilisation, donc l'expression d'affectation sera alors évaluée.
Un nom qualifié par def est évalué en remplaçant le nom et son expression RHS à chaque fois que le nom apparaît dans le programme. Par conséquent, ce remplacement sera exécuté partout où le nom apparaît dans votre programme.
Un nom qualifié par val est évalué immédiatement lorsque le contrôle atteint son expression RHS. Par conséquent, chaque fois que le nom apparaît dans l'expression, il sera considéré comme la valeur de cette évaluation.
Un nom qualifié par lazy val suit la même politique que celle de la qualification val à l'exception que son RHS sera évalué uniquement lorsque le contrôle atteint le point où le nom est utilisé pour la première fois
Devrait signaler un écueil potentiel en ce qui concerne l'utilisation de val lorsque vous travaillez avec des valeurs inconnues jusqu'à l'exécution.
Prends pour exemple, request: HttpServletRequest
Si vous deviez dire:
val foo = request accepts "foo"
Vous obtiendrez une exception de pointeur nul au moment de l'initialisation de val, la demande n'a pas de foo (ne sera connue qu'au moment de l'exécution).
Ainsi, en fonction des frais d'accès/de calcul, def ou val paresseux sont alors des choix appropriés pour les valeurs déterminées à l'exécution; cela, ou un val qui est lui-même une fonction anonyme qui récupère les données d'exécution (bien que ce dernier semble un peu plus le cas Edge)