我能想到两个不同之处
抽象类可以具有构造函数参数以及类型参数。特征只能有类型参数。有一些讨论认为,将来甚至 trait 也可以具有构造函数参数 抽象类可以与 Java 完全互操作。您可以在没有任何包装器的情况下从 Java 代码中调用它们。只有在不包含任何实现代码的情况下,Traits 才能完全互操作
Scala 编程中有一个名为 "To trait, or not to trait?" 的部分解决了这个问题。由于第 1 版可以在线获取,我希望可以在这里引用整个内容。 (任何认真的 Scala 程序员都应该买这本书):
每当您实现可重用的行为集合时,您都必须决定是要使用特征还是抽象类。没有固定的规则,但本节包含一些需要考虑的指导方针。如果该行为不会被重用,则将其设为具体类。毕竟这不是可重用的行为。如果它可以在多个不相关的类中重用,请将其设为 trait。只有特征可以混合到类层次结构的不同部分。如果要在 Java 代码中继承它,请使用抽象类。由于带有代码的特征没有与 Java 相似的类似物,因此从 Java 类中的特征继承往往很尴尬。同时,从 Scala 类继承与从 Java 类继承完全一样。作为一个例外,只有抽象成员的 Scala trait 会直接转换为 Java 接口,因此即使您希望 Java 代码继承自它,您也应该可以随意定义此类 trait。有关一起使用 Java 和 Scala 的更多信息,请参阅第 29 章。如果您计划以编译的形式分发它,并且您希望外部组编写继承自它的类,您可能倾向于使用抽象类。问题是当一个 trait 获得或失去一个成员时,任何从它继承的类都必须重新编译,即使它们没有改变。如果外部客户端只会调用行为,而不是从它继承,那么使用特征就可以了。如果效率非常重要,请倾向于使用类。大多数 Java 运行时使类成员的虚拟方法调用比接口方法调用更快。特征被编译为接口,因此可能会产生轻微的性能开销。但是,只有在您知道所讨论的特征构成性能瓶颈并且有证据表明使用类实际上可以解决问题时,您才应该做出此选择。如果您仍然不知道,在考虑了上述内容之后,请先将其作为一个特征。您以后可以随时更改它,并且通常使用特征可以打开更多选项。
正如@Mushtaq Ahmed 提到的,特征不能将任何参数传递给类的主构造函数。
另一个区别是 super
的处理。
类和特征之间的另一个区别是,在类中,超级调用是静态绑定的,而在特征中,它们是动态绑定的。如果你在一个类中编写 super.toString,你就知道将调用哪个方法实现。但是,当您在 trait 中编写相同的内容时,为 super 调用调用的方法实现在定义 trait 时是未定义的。
有关更多详细信息,请参阅 Chapter 12 的其余部分。
编辑 1 (2013):
与特征相比,抽象类的行为方式存在细微差别。线性化规则之一是它保留了类的继承层次结构,这倾向于将抽象类推到链中的后面,而特征可以很高兴地混入其中。在某些情况下,实际上最好位于类线性化的后面位置,因此可以使用抽象类。请参阅constraining class linearization (mixin order) in Scala。
编辑 2(2018 年):
从 Scala 2.12 开始,trait 的二进制兼容性行为发生了变化。在 2.12 之前,向 trait 添加或删除成员需要重新编译继承该 trait 的所有类,即使这些类没有更改。这是由于 JVM 中对特征进行编码的方式。
从 Scala 2.12 开始,traits compile to Java interfaces,所以要求放宽了一点。如果 trait 执行以下任何操作,其子类仍需要重新编译:
定义字段(val 或 var,但可以使用常量 – 没有结果类型的最终 val)在主体中调用超级初始化语句 扩展依赖于线性化的类以在正确的超级特征中找到实现
但是如果特征没有,您现在可以在不破坏二进制兼容性的情况下对其进行更新。
If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine
- 有人可以解释这里有什么区别吗? extends
与 with
?
extends
和 with
之间绝对没有语义差异。它是纯粹的语法。如果您从多个模板继承,第一个获得 extend
,所有其他模板获得 with
,仅此而已。将 with
视为逗号:class Foo extends Bar, Baz, Qux
。
除了不能直接扩展多个抽象类,但可以将多个特征混合到一个类之外,值得一提的是特征是可堆叠的,因为特征中的超级调用是动态绑定的(它指的是一个类或特征混合之前当前一个)。
来自 Thomas 在 Difference between Abstract Class and Trait 中的回答:
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 Enumerable
时,我不会称它们为 behaviour,而只是与一个特性相关的功能。
一个类可以继承多个特征,但只能继承一个抽象类。抽象类可以具有构造函数参数以及类型参数。特征只能有类型参数。例如,你不能说 trait t(i: Int) { }; i 参数是非法的。抽象类可以与 Java 完全互操作。您可以在没有任何包装器的情况下从 Java 代码中调用它们。只有在不包含任何实现代码的情况下,特征才能完全互操作。