A def
pode ser implementado por um def
, um val
, um lazy val
ou um object
. Portanto, é a forma mais abstrata de definir um membro. Como as características são geralmente interfaces abstratas, dizer que você deseja val
é dizer como a implementação deve se comportar. Se você solicitar a val
, uma classe de implementação não poderá usar a def
.
A val
é necessário apenas se você precisar de um identificador estável, por exemplo, para um tipo dependente do caminho. Isso é algo que você geralmente não precisa.
Comparar:
trait Foo { def bar: Int }
object F1 extends Foo { def bar = util.Random.nextInt(33) }
class F2(val bar: Int) extends Foo
object F3 extends Foo {
lazy val bar = {
Thread.sleep(5000)
42
}
}
Se você tinha
trait Foo { val bar: Int }
você não seria capaz de definir F1
ou F3
.
Ok, e para confundi-lo e responder @ om-nom-nom - usar abstract val
pode causar problemas de inicialização:
trait Foo {
val bar: Int
val schoko = bar + bar
}
object Fail extends Foo {
val bar = 33
}
Fail.schoko
Este é um problema feio que, em minha opinião pessoal, deveria desaparecer nas futuras versões do Scala corrigindo-o no compilador, mas sim, atualmente esta também é uma razão pela qual não se deve usar val
s abstratos .
Editar (janeiro de 2016): Você tem permissão para substituir uma val
declaração abstrata por uma lazy val
implementação, de modo que também evite a falha de inicialização.
val
por alazy val
. Sua afirmação de que você não seria capaz de criarF3
sebar
fosse umval
não está correta. Dito isso, membros abstratos em traços devem sempre serdef
'sval schoko = bar + bar
porlazy val schoko = bar + bar
. Essa é uma maneira de ter algum controle sobre a ordem de inicialização. Além disso, usar emlazy val
vez dedef
na classe derivada evita a recomputação.val bar: Int
paradef bar: Int
Fail.schoko
ainda é zero.Prefiro não usar
val
em traits porque a declaração val tem uma ordem de inicialização confusa e não intuitiva. Você pode adicionar um traço à hierarquia que já está funcionando e isso quebraria todas as coisas que funcionavam antes, veja meu tópico: por que usar val simples em classes não finaisVocê deve manter todas as coisas sobre o uso dessas declarações val em mente, o que eventualmente leva a um erro.
Atualize com um exemplo mais complicado
Mas há momentos em que você não consegue evitar o uso
val
. Como @ 0__ mencionou, às vezes você precisa de um identificador estável edef
não é.Eu daria um exemplo para mostrar o que ele estava falando:
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 produz o erro:
StableIdentifier.scala:14: error: stable identifier required, but Access2.this.holder found. def access : holder.Inner =
Se você parar um minuto para pensar, entenderá que o compilador tem um motivo para reclamar. No
Access2.access
caso, ele não poderia derivar o tipo de retorno por nenhum meio.def holder
significa que pode ser implementado de forma ampla. Ele poderia retornar portadores diferentes para cada chamada e esses portadores incorporariamInner
tipos diferentes . Mas a máquina virtual Java espera que o mesmo tipo seja retornado.fonte
Sempre usar def parece um pouco estranho, já que algo assim não funcionará:
trait Entity { def id:Int} object Table { def create(e:Entity) = {e.id = 1 } }
Você obterá o seguinte erro:
error: value id_= is not a member of Entity
fonte
var
. A questão é que, se são campos, devem ser designados como tal. Só acho que ter tudo comodef
é míope.var
permite que você interrompa o encapsulamento. Mas usar adef
(ou aval
) é preferível a uma variável global. Eu acho que o que você está procurando é algo comocase class ConcreteEntity(override val id: Int) extends Entity
que você possa criá-lo a partir dedef create(e: Entity) = ConcreteEntity(1)
Isso é mais seguro do que quebrar o encapsulamento e permitir que qualquer classe altere a Entidade.