A def
puede implementarse mediante a def
, a val
, a lazy val
o an object
. Entonces es la forma más abstracta de definir un miembro. Dado que los rasgos suelen ser interfaces abstractas, decir que quieres una val
es decir cómo debería funcionar la implementación. Si solicita a val
, una clase de implementación no puede usar a def
.
A val
solo es necesario si necesita un identificador estable, por ejemplo, para un tipo dependiente de la ruta. Eso es algo que normalmente no necesitas.
Comparar:
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 tuvieras
trait Foo { val bar: Int }
no podrías definir F1
o F3
.
Ok, y para confundirte y responder @ om-nom-nom, el uso de val
s abstractos puede causar problemas de inicialización:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko
Este es un problema feo que, en mi opinión personal, debería desaparecer en futuras versiones de Scala al arreglarlo en el compilador, pero sí, actualmente esta es también una razón por la que uno no debería usar val
s abstractos .
Editar (enero de 2016): se le permite anular una val
declaración abstracta con una lazy val
implementación, por lo que también evitaría el error de inicialización.
val
con unlazy val
. Su afirmación de que no podría crearF3
sibar
fuera unval
no es correcta. Dicho esto, los miembros abstractos en rasgos siempre deben serdef
'sval schoko = bar + bar
conlazy val schoko = bar + bar
. Esa es una forma de tener cierto control sobre el orden de inicialización. Además, usar enlazy val
lugar dedef
en la clase derivada evita el recálculo.val bar: Int
adef bar: Int
Fail.schoko
sigue siendo cero.Prefiero no usar
val
en rasgos porque la declaración de val tiene un orden de inicialización poco claro y no intuitivo. Puede agregar un rasgo a la jerarquía que ya funciona y rompería todas las cosas que funcionaron antes, vea mi tema: por qué usar val simple en clases no finalesDebe tener en cuenta todo lo relacionado con el uso de estas declaraciones val que eventualmente lo conducen a un error.
Actualizar con un ejemplo más complicado
Pero hay momentos en los que no puede evitar el uso
val
. Como @ 0__ mencionó, a veces necesitas un identificador estable ydef
no lo es.Daría un ejemplo para mostrar de qué estaba hablando:
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 }
Este código produce el error:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found. def access : holder.Inner =
Si se toma un minuto para pensar, comprenderá que el compilador tiene una razón para quejarse. En el
Access2.access
caso, no podría derivar el tipo de retorno de ninguna manera.def holder
significa que podría implementarse de manera amplia. Podría devolver diferentes titulares para cada llamada y los titulares incorporarían diferentesInner
tipos. Pero la máquina virtual Java espera que se devuelva el mismo tipo.fuente
Usar siempre def parece un poco incómodo ya que algo como esto no funcionará:
trait Entity { def id:Int} object Table { def create(e:Entity) = {e.id = 1 } }
Obtendrá el siguiente error:
error: value id_= is not a member of Entity
fuente
var
. El caso es que, si son campos, deberían designarse como tales. Creo que tener todo comodef
es miope.var
permite romper la encapsulación. Pero se prefiere usar adef
(o aval
) sobre una variable global. Creo que lo que estás buscando es algocase class ConcreteEntity(override val id: Int) extends Entity
así como para poder crearlo desde.def create(e: Entity) = ConcreteEntity(1)
Esto es más seguro que romper la encapsulación y permitir que cualquier clase cambie de Entidad.