使用抽象类而不是特征的优点是什么?

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

104372 次浏览

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

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

我能想到两个不同点

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

无论价值如何,Odersky等人的Scala编程建议,当你怀疑时,你使用trait。如果需要,您可以随时将它们更改为抽象类。

Scala编程中,作者说抽象类是一个经典的面向对象的“is-a”关系,而trait是一种scala方式的组合。

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

当你实现一个可重用的行为集合时,你将 必须决定是使用trait还是抽象类。 没有固定的规则,但本节包含一些指导方针 考虑。< / p >

如果该行为不会被重用,然后使它成为一个具体类。它 毕竟不是可重用的行为。

如果它可以在多个不相关的类中重用,使其成为trait。 只有特质可以混合到职业层次的不同部分

如果你想在Java代码中继承它,使用抽象类。 由于带有代码的特征没有类似于Java的东西,所以它往往是类似的 从Java类中的trait继承是很尴尬的。从 与此同时,Scala类就像从Java类继承一样。 作为一个例外,只有抽象成员的Scala特征会被转换 直接到Java接口,因此您可以随意定义这样的接口 特征,即使您希望Java代码继承它。参见第29章

.

.

如果您计划以编译形式发布它,你期望外面 组来编写从它继承的类,您可能倾向于 使用抽象类。问题是,当一个特质得到或失去 成员,从它继承的任何类都必须重新编译,即使 他们没有改变。如果外部客户只会调用进去 行为,而不是从它继承,那么使用trait是很好的

如果效率很重要的话,倾向于使用类。大多数Java 运行时使类成员的虚方法调用更快 操作比接口方法调用要好。特征被编译成 接口,因此可能会付出轻微的性能开销。 然而,只有在你了解这个特质的情况下,你才应该做出这个选择 在问题中构成了性能瓶颈并有证据

如果你还不知道,考虑完以上,然后开始 让它成为一种特质。一般来说,你可以随时更改它

.使用trait会有更多的选择

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

另一个区别是super的处理。

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

更多细节请参见第十二章的其余部分。

编辑1 (2013):

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

编辑2 (2018):

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

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

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

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