Mus*_*med 363
我可以想到两个不同之处
Eug*_*ota 202
Scala编程中有一节称为"Trait,还是不特质?" 这解决了这个问题.由于第1版可以在线获取,我希望可以在这里引用整个内容.(任何严肃的Scala程序员都应该买这本书):
每当您实现可重用的行为集合时,您将必须决定是否要使用特征或抽象类.没有确定的规则,但本节包含一些需要考虑的准则.
如果不再重用该行为,则将其作为具体类.毕竟,这不是可重复使用的行为.
如果它可能在多个不相关的类中重用,那么将其作为特征.只有特征可以混合到类层次结构的不同部分.
如果要在Java代码中继承它,请使用抽象类.由于具有代码的特征没有密切的Java模拟,因此从Java类中的特征继承往往很难.与此同时,从Scala类继承就像从Java类继承一样.作为一个例外,只有抽象成员的Scala特征直接转换为Java接口,因此即使您希望Java代码继承它,您也应该随意定义这些特征.有关一起使用Java和Scala的更多信息,请参见第29章.
如果您计划以编译形式分发它,并且您希望外部组编写从其继承的类,您可能倾向于使用抽象类.问题是当一个特征获得或失去一个成员时,任何从中继承的类都必须重新编译,即使它们没有改变.如果外部客户端只调用行为,而不是继承它,那么使用特征就可以了.
如果效率非常重要,那就倾向于使用一门课程.大多数Java运行时使类成员的虚方法调用比接口方法调用更快.Traits被编译到接口,因此可能会产生轻微的性能开销.但是,只有当您知道所讨论的特征构成性能瓶颈并且有证据表明使用类实际上解决了问题时,才应该做出此选择.
如果您在考虑上述情况后仍然不知道,那么首先将其作为特征.您可以随时更改它,通常使用特征可以打开更多选项.
正如@Mushtaq Ahmed所提到的,特征不能将任何参数传递给类的主构造函数.
另一个区别是治疗super.
类和特征之间的另一个区别是,在类中,
super调用是静态绑定的,在特征中,它们是动态绑定的.如果您super.toString在类中编写,则确切地知道将调用哪个方法实现.但是,当您在特征中编写相同的内容时,在定义特征时,未定义调用超级调用的方法实现.
有关详细信息,请参阅第12章的其余部分.
编辑1(2013):
与特征相比,抽象类的行为方式存在细微差别.其中一个线性化规则是它保留了类的继承层次结构,它往往会在链中稍后推送抽象类,而特征可以很好地混合在一起.在某些情况下,实际上最好是在类线性化的后一个位置,因此可以使用抽象类.请参阅在Scala中约束类线性化(mixin顺序).
编辑2(2018):
从Scala 2.12开始,trait的二进制兼容性行为发生了变化.在2.12之前,向特征添加或删除成员需要重新编译继承特征的所有类,即使类没有更改.这是由于特征在JVM中编码的方式.
从Scala 2.12开始,traits 编译为Java接口,因此需求略有放松.如果特征执行以下任何操作,则其子类仍需要重新编译:
- 定义字段(
val或者var,但是常量是正确的 -final val没有结果类型)- 调用
super- 正文中的初始化语句
- 扩展课程
- 依靠线性化来找到正确的超级实现
但如果特性没有,您现在可以在不破坏二进制兼容性的情况下更新它.
Nem*_*ric 20
除了你不能直接扩展多个抽象类,但你可以将多个特征混合到一个类中之外,值得一提的是特征是可堆叠的,因为特征中的超级调用是动态绑定的(它指的是之前混合的类或特征目前的一个).
从托马斯在抽象类和特质之间的差异中的答案:
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
Run Code Online (Sandbox Code Playgroud)