Respostas:
Eu posso pensar em duas diferenças
Há uma seção em Programação em Scala chamada "Caracterizar ou não caracterizar?" que aborda esta questão. Como a 1ª edição está disponível on-line, espero que seja correto citar tudo aqui. (Qualquer programador Scala sério deve comprar o livro):
Sempre que você implementar uma coleção reutilizável de comportamento, terá que decidir se deseja usar uma característica ou uma classe abstrata. Não existe uma regra firme, mas esta seção contém algumas diretrizes a serem consideradas.
Se o comportamento não for reutilizado , torne-o uma classe concreta. Afinal, não é um comportamento reutilizável.
Se puder ser reutilizado em várias classes não relacionadas , faça disso uma característica. Somente traços podem ser misturados em diferentes partes da hierarquia de classes.
Se você deseja herdar dele no código Java , use uma classe abstrata. Como as características com código não têm um análogo Java próximo, tende a ser estranho herdar uma característica em uma classe Java. Enquanto isso, herdar uma classe Scala é exatamente como herdar de uma classe Java. Como uma exceção, uma característica do Scala com apenas membros abstratos se traduz diretamente em uma interface Java, portanto, você deve definir tais características, mesmo que espere que o código Java seja herdado. Consulte o Capítulo 29 para obter mais informações sobre como trabalhar com Java e Scala juntos.
Se você planeja distribuí-lo na forma compilada , e espera que grupos externos escrevam classes herdadas, você pode usar uma classe abstrata. O problema é que, quando uma característica ganha ou perde um membro, todas as classes que herdam dela devem ser recompiladas, mesmo que não tenham sido alteradas. Se os clientes externos apenas chamarem o comportamento, em vez de herdá-lo, usar uma característica é bom.
Se a eficiência é muito importante , incline-se a usar uma classe. A maioria dos tempos de execução Java torna a invocação de método virtual de um membro da classe uma operação mais rápida que a invocação de método de interface. As características são compiladas nas interfaces e, portanto, podem causar uma pequena sobrecarga de desempenho. No entanto, você deve fazer essa escolha apenas se souber que o traço em questão constitui um gargalo de desempenho e tiver evidências de que o uso de uma classe resolve o problema.
Se você ainda não sabe , depois de considerar o exposto, comece fazendo isso como uma característica. Você sempre pode alterá-lo mais tarde e, em geral, o uso de uma característica mantém mais opções em aberto.
Como o @Mushtaq Ahmed mencionou, uma característica não pode ter nenhum parâmetro passado para o construtor primário de uma classe.
Outra diferença é o tratamento de super
.
A outra diferença entre classes e características é que, enquanto nas classes, as
super
chamadas são vinculadas estaticamente, nas características, elas são vinculadas dinamicamente. Se você escrevesuper.toString
em uma classe, sabe exatamente qual implementação de método será chamada. Quando você escreve a mesma coisa em uma característica, no entanto, a implementação do método a ser chamada para a super chamada é indefinida quando você define a característica.
Veja o restante do capítulo 12 para mais detalhes.
Edição 1 (2013):
Há uma diferença sutil na maneira como as classes abstratas se comportam em comparação com as características. Uma das regras de linearização é que ela preserva a hierarquia de herança das classes, que tende a empurrar classes abstratas mais tarde na cadeia, enquanto os traços podem ser misturados com alegria. Em certas circunstâncias, é realmente preferível estar na última posição da linearização de classes , então classes abstratas podem ser usadas para isso. Consulte Linearização de classe restritiva (ordem de mixagem) em Scala .
Edição 2 (2018):
No Scala 2.12, o comportamento de compatibilidade binária do traço mudou. Antes da versão 2.12, adicionar ou remover um membro à característica exigia a recompilação de todas as classes que herdam a característica, mesmo que as classes não tenham sido alteradas. Isso se deve à maneira como as características foram codificadas na JVM.
No Scala 2.12, os traços são compilados nas interfaces Java , portanto, o requisito diminuiu um pouco. Se a característica seguir um destes procedimentos, suas subclasses ainda exigirão recompilação:
- definindo campos (
val
ouvar
, mas uma constante está ok -final val
sem tipo de resultado)- chamando
super
- instruções inicializador no corpo
- estendendo uma classe
- confiando na linearização para encontrar implementações no super estreito certo
Mas se a característica não existir, você poderá atualizá-la sem interromper a compatibilidade binária.
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- Alguém poderia explicar qual é a diferença aqui? extends
vs with
?
extends
e with
. É puramente sintático. Se você herdar de vários modelos, o primeiro recebe extend
, todos os outros recebem with
, é isso. Pense with
como uma vírgula: class Foo extends Bar, Baz, Qux
.
Por tudo o que vale a pena, a Programação em Scala de Odersky et al recomenda que, quando você duvida, use traços. Você sempre pode transformá-los em classes abstratas posteriormente, se necessário.
Além do fato de que você não pode estender diretamente várias classes abstratas, mas você pode misturar várias características em uma classe, vale mencionar que as características são empilháveis, pois as super chamadas em uma característica são dinamicamente ligadas (está se referindo a uma classe ou característica misturada antes atual).
Da resposta de Thomas na diferença entre classe abstrata e característica :
trait A{
def a = 1
}
trait X extends A{
override def a = {
println("X")
super.a
}
}
trait Y extends A{
override def a = {
println("Y")
super.a
}
}
scala> val xy = new AnyRef with X with Y
xy: java.lang.Object with X with Y = $anon$1@6e9b6a
scala> xy.a
Y
X
res0: Int = 1
scala> val yx = new AnyRef with Y with X
yx: java.lang.Object with Y with X = $anon$1@188c838
scala> yx.a
X
Y
res1: Int = 1
Ao estender uma classe abstrata, isso mostra que a subclasse é de um tipo semelhante. Este não é necessariamente o caso ao usar traços, eu acho.
Na Programming Scala, os autores dizem que as classes abstratas fazem um relacionamento clássico "is-a" orientado a objetos, enquanto os traços são um modo de composição de scala.
As classes abstratas podem conter comportamento - elas podem ser parametrizadas com argumentos do construtor (que não podem ser características) e representam uma entidade que funciona. Os traços representam apenas um único recurso, uma interface de uma funcionalidade.
trait Enumerable
com muitas funções auxiliares, eu não as chamaria de comportamento, mas apenas de funcionalidade conectada a um recurso.