【发布时间】:2010-12-31 18:05:22
【问题描述】:
使用抽象类而不是特征有什么好处(除了性能)?在大多数情况下,抽象类似乎可以被特征替换。
【问题讨论】:
使用抽象类而不是特征有什么好处(除了性能)?在大多数情况下,抽象类似乎可以被特征替换。
【问题讨论】:
我能想到两个不同之处
【讨论】:
Scala 编程中有一个名为"To trait, or not to trait?" 的部分解决了这个问题。由于第 1 版可以在线获取,我希望可以在这里引用整个内容。 (任何认真的 Scala 程序员都应该买这本书):
每当您实现可重用的行为集合时,您将 必须决定是要使用特征还是抽象类。 没有固定的规则,但本节包含一些指导方针 考虑一下。
如果该行为不会被重用,则将其设为具体类。它 毕竟不是可重用的行为。
如果它可以在多个不相关的类中重复使用,请将其设为 trait。 只有特质可以混合到类层次结构的不同部分中。
如果您想在 Java 代码中继承它,请使用抽象类。 由于带有代码的特征没有与 Java 相似的类似物,因此它往往是 从 Java 类中继承特征很尴尬。继承自 同时,Scala 类与从 Java 类继承完全一样。 作为一个例外,只有抽象成员的 Scala 特征可以翻译 直接连接到 Java 接口,所以你应该随意定义这样的 即使您希望 Java 代码从它继承,也可以使用特征。见第 29 章 了解有关一起使用 Java 和 Scala 的更多信息。
如果您打算以编译的形式分发它,并且您希望在外部 组来编写继承自它的类,您可能倾向于 使用抽象类。问题是当一个特征获得或失去时 一个成员,任何继承自它的类都必须重新编译,即使 他们没有改变。如果外部客户只会调用 行为,而不是从它继承,然后使用特征就可以了。
如果效率很重要,请倾向于使用类。大多数Java 运行时使类成员的虚拟方法调用更快 操作比接口方法调用。特征被编译为 接口,因此可能会支付轻微的性能开销。 但是,只有当您知道该特征时,您才应该做出此选择 有问题构成性能瓶颈并有证据 使用类实际上可以解决问题。
如果你还是不知道,在考虑了以上之后,那就从 使它成为一种特征。您以后可以随时更改它,一般来说 使用 trait 会保留更多选项。
正如@Mushtaq Ahmed 所提到的,特征不能将任何参数传递给类的主构造函数。
另一个区别是super的处理方式。
类和特征之间的另一个区别是,在类中,
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,但可以使用常量 -final val没有结果类型)- 致电
super- 正文中的初始化语句
- 扩展类
- 依靠线性化在正确的超特征中找到实现
但如果特征没有,您现在可以在不破坏二进制兼容性的情况下更新它。
【讨论】:
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。
不管它值多少钱,Odersky 等人的Programming in Scala 建议,当你怀疑时,你可以使用特征。如果需要,您可以随时将它们更改为抽象类。
【讨论】:
除了不能直接扩展多个抽象类,但可以将多个特征混合到一个类中之外,值得一提的是特征是可堆叠的,因为特征中的超级调用是动态绑定的(它指的是一个类或特征在当前特征之前混合)。
来自 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
【讨论】:
当扩展一个抽象类时,这表明子类是类似的。我认为在使用特征时不一定是这种情况。
【讨论】:
在Programming Scala 中,作者说抽象类构成了一种经典的面向对象的“is-a”关系,而特征是一种 scala 组合方式。
【讨论】:
抽象类可以包含行为 - 它们可以使用构造函数参数(特征不能)进行参数化并表示一个工作实体。相反,特征只代表一个特性,一个功能的接口。
【讨论】:
trait Enumerable 时,我不会称它们为 行为,而只是与一个特性相关的功能。
【讨论】: