使用抽象类而不是trait的优势是什么(除了性能)?在大多数情况下,抽象类似乎可以被特征所取代。


当扩展一个抽象类时,这表明子类是类似的。我认为在使用特质时并不一定是这样。


抽象类可以包含行为——它们可以用构造函数参数化(trait不能)并表示一个工作实体。trait只是代表一个单一的特性,一个功能的界面。


我能想到两个不同点

抽象类可以有构造函数形参和类型形参。trait只能有类型参数。有一些讨论说,将来甚至trait也可以有构造函数形参 抽象类与Java完全可互操作。您可以从Java代码中调用它们,而不需要任何包装器。trait只有在不包含任何实现代码的情况下才完全可互操作


无论如何,Odersky等人在《Scala编程》一书中建议,当你怀疑时,就使用trait。如果需要,您可以随时将它们更改为抽象类。


在Programming Scala中,作者说抽象类构成了典型的面向对象的“is-a”关系,而特征则是一种Scala组合方式。


在《Scala编程》中有一节叫做“trait还是不trait?”,它解决了这个问题。由于第一个版本可以在网上找到,我希望可以在这里引用整篇文章。(任何认真的Scala程序员都应该买这本书):

Whenever you implement a reusable collection of behavior, you will have to decide whether you want to use a trait or an abstract class. There is no firm rule, but this section contains a few guidelines to consider. If the behavior will not be reused, then make it a concrete class. It is not reusable behavior after all. If it might be reused in multiple, unrelated classes, make it a trait. Only traits can be mixed into different parts of the class hierarchy. If you want to inherit from it in Java code, use an abstract class. Since traits with code do not have a close Java analog, it tends to be awkward to inherit from a trait in a Java class. Inheriting from a Scala class, meanwhile, is exactly like inheriting from a Java class. As one exception, a Scala trait with only abstract members translates directly to a Java interface, so you should feel free to define such traits even if you expect Java code to inherit from it. See Chapter 29 for more information on working with Java and Scala together. If you plan to distribute it in compiled form, and you expect outside groups to write classes inheriting from it, you might lean towards using an abstract class. The issue is that when a trait gains or loses a member, any classes that inherit from it must be recompiled, even if they have not changed. If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine. If efficiency is very important, lean towards using a class. Most Java runtimes make a virtual method invocation of a class member a faster operation than an interface method invocation. Traits get compiled to interfaces and therefore may pay a slight performance overhead. However, you should make this choice only if you know that the trait in question constitutes a performance bottleneck and have evidence that using a class instead actually solves the problem. If you still do not know, after considering the above, then start by making it as a trait. You can always change it later, and in general using a trait keeps more options open.

正如@Mushtaq Ahmed提到的,trait不能将任何参数传递给类的主构造函数。

另一个区别是对super的处理。

类和特征之间的另一个区别是,在类中,超调用是静态绑定的,而在特征中,它们是动态绑定的。如果你写super。类中的toString,您可以确切地知道将调用哪个方法实现。但是,当您在trait中编写相同的内容时,在定义trait时,为超级调用调用的方法实现是未定义的。

更多细节请参见第12章的剩余部分。

编辑1 (2013):

抽象类的行为方式与特征相比有细微的不同。线性化规则之一是它保留了类的继承层次结构,这倾向于将抽象类推到链的后面,而特征可以愉快地混合在一起。在某些情况下,在类线性化的后一个位置实际上更可取,因此可以使用抽象类。参见Scala中约束类线性化(mixin顺序)。

编辑2 (2018):

从Scala 2.12开始,trait的二进制兼容性行为发生了变化。在2.12之前,向trait中添加或删除成员需要重新编译继承该trait的所有类,即使这些类没有改变。这是由于JVM中特征编码的方式。

从Scala 2.12开始,特征可以编译为Java接口,因此需求有所放松。如果trait做了以下任何一项,它的子类仍然需要重新编译:

定义字段(val或var,但可以是常量-没有结果类型的final val) 调用超 初始化语句 扩展一个类 依靠线性化在正确的超特征中找到实现

但是如果trait没有,你可以在不破坏二进制兼容性的情况下更新它。


除了不能直接扩展多个抽象类,但可以将多个trait混合到一个类中之外,值得一提的是,trait是可堆叠的,因为trait中的超级调用是动态绑定的(它引用的是在当前类或trait之前混合的类或trait)。

摘自Thomas在《抽象阶级与特质的区别》中的回答:

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

一个类可以继承多个特征,但只能继承一个抽象类。 抽象类可以有构造函数形参和类型形参。trait只能有类型参数。例如,你不能说trait t(i: Int) {};参数I不合法。 抽象类与Java完全可互操作。您可以从Java代码中调用它们,而不需要任何包装器。trait只有在不包含任何实现代码的情况下才完全可互操作。