Je parcourais les effective scala slides et il mentionne sur la diapositive 10 de ne jamais utiliser val
dans un trait
pour les membres abstraits et utilisez def
à la place. La diapositive ne mentionne pas en détail pourquoi l'utilisation de l'abrégé val
dans un trait
est un anti-modèle. J'apprécierais que quelqu'un puisse expliquer les meilleures pratiques autour de l'utilisation de val vs def dans un trait pour les méthodes abstraites
Un def
peut être implémenté par un def
, un val
, un lazy val
ou 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
dit comment l'implémentation devrait faire. Si vous demandez un val
, une classe d'implémentation ne peut pas utiliser un def
.
Un 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) } // ok
class F2(val bar: Int) extends Foo // ok
object F3 extends Foo {
lazy val bar = { // ok
Thread.sleep(5000) // really heavy number crunching
42
}
}
Si tu avais
trait Foo { val bar: Int }
vous ne pourriez pas 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 // zero!!
C'est un vilain problème qui, à mon avis, devrait disparaître à l'avenir Scala versions en le corrigeant dans le compilateur, mais oui, actuellement, c'est aussi une raison pour laquelle il ne faut pas utiliser l'abstrait val
s.
Edit (Jan 2016): Vous êtes autorisé à remplacer une déclaration abstraite val
par une lazy val
l'implémentation, ce qui empêcherait également l'échec de l'initialisation.
Je préfère ne pas 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 à une hiérarchie qui fonctionne déjà et cela casserait toutes les choses qui fonctionnaient auparavant, voir mon sujet: pourquoi utiliser plain val dans les classes non finales
Vous devez garder à l'esprit tout ce qui concerne l'utilisation de ces déclarations de valeur qui vous conduiront éventuellement à une erreur.
Mais il y a des moments où vous ne pouvez pas éviter d'utiliser val
. Comme @ 0__ l'avait mentionné parfois, vous avez besoin d'un identifiant stable et def
n'en fait pas partie.
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 comprendrez que le compilateur a une raison de se plaindre. Dans le Access2.access
cas où il ne pouvait 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 ces titulaires incorporeraient différents types Inner
. Mais Java attend que le même type soit renvoyé.