A def
peut être implémenté soit par a def
, a val
, a lazy val
ou an object
. C'est donc la forme la plus abstraite de définition d'un membre. Puisque les traits sont généralement des interfaces abstraites, dire que vous voulez un, val
c'est dire comment l'implémentation doit fonctionner. Si vous demandez a val
, une classe d'implémentation ne peut pas utiliser a def
.
A val
n'est nécessaire que si vous avez besoin d'un identifiant stable, par exemple pour un type dépendant du chemin. C'est quelque chose dont vous n'avez généralement pas besoin.
Comparer:
trait Foo { def bar: Int }
object F1 extends Foo { def bar = util.Random.nextInt(33) }
class F2(val bar: Int) extends Foo // ok
object F3 extends Foo {
lazy val bar = {
Thread.sleep(5000)
42
}
}
Si tu avais
trait Foo { val bar: Int }
vous ne seriez pas en mesure de définir F1
ou F3
.
Ok, et pour vous embrouiller et répondre @ om-nom-nom - l'utilisation de val
s abstraits peut causer des problèmes d'initialisation:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko
C'est un problème horrible qui, à mon avis personnel, devrait disparaître dans les futures versions de Scala en le corrigeant dans le compilateur, mais oui, actuellement c'est aussi une raison pour laquelle on ne devrait pas utiliser de résumé val
.
Edit (Jan 2016): Vous êtes autorisé à remplacer une val
déclaration abstraite avec une lazy val
implémentation, ce qui empêcherait également l'échec de l'initialisation.
val
par unlazy val
. Votre affirmation selon laquelle vous ne seriez pas en mesure de créerF3
sibar
c'était unval
n'est pas correcte. Cela dit, les membres abstraits dans les traits devraient toujours êtredef
'sval schoko = bar + bar
parlazy val schoko = bar + bar
. C'est une façon d'avoir un certain contrôle sur l'ordre d'initialisation. De plus, utiliserlazy val
au lieu dedef
dans la classe dérivée évite le recalcul.val bar: Int
àdef bar: Int
Fail.schoko
est toujours zéro.Je préfère ne pas l'utiliser
val
dans les traits car la déclaration val a un ordre d'initialisation peu clair et non intuitif. Vous pouvez ajouter un trait à la hiérarchie déjà opérationnelle et cela casserait toutes les choses qui fonctionnaient auparavant, voir mon sujet: pourquoi utiliser un val simple dans des classes non finalesVous devez garder à l'esprit tout ce qui concerne l'utilisation de ces déclarations val qui finissent par vous conduire à une erreur.
Mettre à jour avec un exemple plus compliqué
Mais il y a des moments où vous ne pouvez pas éviter d'utiliser
val
. Comme @ 0__ l'avait mentionné, vous avez parfois besoin d'un identifiant stable etdef
ne l'est pas.Je donnerais un exemple pour montrer de quoi il parlait:
trait Holder { type Inner val init : Inner } class Access(val holder : Holder) { val access : holder.Inner = holder.init } trait Access2 { def holder : Holder def access : holder.Inner = holder.init }
Ce code produit l'erreur:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found. def access : holder.Inner =
Si vous prenez une minute pour penser, vous comprendriez que le compilateur a une raison de se plaindre. Dans le
Access2.access
cas où il ne pourrait en aucun cas dériver le type de retour.def holder
signifie qu'il pourrait être mis en œuvre de manière large. Il pourrait renvoyer différents titulaires pour chaque appel et que les titulaires incorporeraient différentsInner
types. Mais la machine virtuelle Java s'attend à ce que le même type soit renvoyé.la source
Toujours utiliser def semble un peu gênant car quelque chose comme ça ne fonctionnera pas:
trait Entity { def id:Int} object Table { def create(e:Entity) = {e.id = 1 } }
Vous obtiendrez l'erreur suivante:
error: value id_= is not a member of Entity
la source
var
. Le fait est que si ce sont des champs, ils doivent être désignés comme tels. Je pense juste avoir tout comme àdef
courte vue.var
vous permet de casser l'encapsulation. Mais l'utilisation de adef
(ou aval
) est préférable à une variable globale. Je pense que ce que vous recherchez est quelque chose commecase class ConcreteEntity(override val id: Int) extends Entity
pour que vous puissiez le créer à partir dedef create(e: Entity) = ConcreteEntity(1)
Ceci est plus sûr que de casser l'encapsulation et de permettre à n'importe quelle classe de changer d'entité.