使用抽象类而不是特征有什么好处?

Ral*_*alf 362 scala traits

使用抽象类而不是特性(除了性能)有什么好处?在大多数情况下,似乎抽象类可以被特征替换.

Mus*_*med 363

我可以想到两个不同之处

  1. 抽象类可以包含构造函数参数以及类型参数.特征只能有类型参数.有一些讨论,将来甚至特征都可以有构造函数参数
  2. 抽象类可与Java完全互操作.你可以从没有任何包装器的Java代码中调用它们.只有当Traits不包含任何实现代码时,它们才能完全互操作

  • 非常重要的附录:一个类可以从多个traits继承,但只能从一个抽象类继承.我认为这应该是开发人员在考虑在几乎所有情况下使用哪个时都要问的第一个问题. (164认同)
  • Per Scala 2.12,一个特性编译成Java 8接口 - http://scala-lang.org/news/2.12.0#traits-compile-to-interfaces. (14认同)
  • 救生员:"只有当Traits不包含任何实现代码时才能完全互操作" (13认同)
  • 想想,Java8中不存在第二个区别. (5认同)
  • abstract - 当集体行为定义或导向一个对象(对象的分支)但仍未编写为(就绪)对象时.特征,当您需要引入功能时,即功能永远不会产生于对象的创建,当对象从隔离中出来并且必须进行通信时,它就会发展或需要. (2认同)

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
  • 正文中的初始化语句
  • 扩展课程
  • 依靠线性化来找到正确的超级实现

但如果特性没有,您现在可以在不破坏二进制兼容性的情况下更新它.

  • `如果外部客户只会调用行为,而不是从中继承,那么使用特征就可以了. - 有人可以解释这里的区别是什么吗?`extends` vs`with`? (2认同)
  • @ 0fnt他的区别与扩展与否无关。他的意思是,如果仅在同一编译中混用特征,则二进制兼容性问题不适用。但是,如果您的API设计为允许用户自己混合特征,那么您将不得不担心二进制兼容性。 (2认同)
  • @0fnt:`extends` 和 `with` 之间绝对没有语义差异。它纯粹是语法上的。如果你从多个模板继承,第一个得到`extend`,所有其他得到`with`,就是这样。将 `with` 视为逗号:`class Foo extends Bar, Baz, Qux`。 (2认同)

Dan*_*ral 76

无论它有什么价值,Odersky等人的Scala编程建议,当你怀疑时,你会使用特征.如果需要,您可以随后将它们更改为抽象类.


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)


pet*_*r p 9

扩展抽象类时,这表明子类是类似的类.我认为,在使用特征时不一定是这种情况.

  • 我只是指代码可理解性. (3认同)

CQQ*_*QQL 7

Scala编程中,作者说抽象类使得经典对象面向"is-a"关系,而traits是一种scala-way的组合方式.