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

时间:2010-01-02 09:03:29

标签: scala traits

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

8 个答案:

答案 0 :(得分:349)

我可以想到两个不同

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

答案 1 :(得分:191)

Scala编程中的一个名为"To trait, or not to trait?"的部分解决了这个问题。由于第1版可以在线获取,我希望可以在这里引用整个内容。 (任何严肃的Scala程序员都应该买这本书):

  

每当您实施可重用的行为集合时,您都会这样做   必须决定是否要使用特征或抽象类。   没有确定的规则,但本节包含一些指导原则   考虑

     

如果行为不会被重用,那么将其作为具体类。它   毕竟不是可重复使用的行为。

     

如果它可能在多个不相关的类中重用,请将其作为特征。   只有特征可以混合到类层次结构的不同部分。

     

如果要在Java代码中继承它,请使用抽象类。   由于具有代码的特征没有密切的Java模拟,因此它往往是   难以从Java类中的特征继承。继承自   与此同时,Scala类与继承Java类完全相同。   作为一个例外,只有抽象成员的Scala特征会被翻译   直接到Java接口,所以你应该随意定义这样的   即使您希望Java代码继承它,也会出现特征。见第29章   有关使用Java和Scala的更多信息。

     

如果您打算以编译形式分发它,并且您希望在外面   要编写继承自它的类,你可能会倾向于   使用抽象类。问题是当特质获得或失败时   一个成员,任何从它继承的类都必须重新编译,即使是   他们没有改变。如果外部客户只会打电话给   行为,而不是从它继承,然后使用特征是好的。

     

如果效率非常重要,请倾向于使用课程。大多数Java   运行时使类成员的虚方法调用更快   操作比接口方法调用。特征被编译到   接口因此可能会产生轻微的性能开销。   但是,只有在知道该特征时才应该做出此选择   有问题构成了性能瓶颈并有证据   而使用类实际上解决了问题。

     

如果您还不知道,在考虑了上述情况后,请先着手   把它作为一种特质。您可以随时更改它,一般情况下   使用特征可以打开更多选项。

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

另一个区别是对super的处理。

  

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

有关详细信息,请参阅Chapter 12的其余部分。

编辑1(2013):

与特征相比,抽象类的行为方式存在细微差别。线性化规则之一是它保留了类的继承层次结构,这往往会在链中稍后推送抽象类,而特征可以很好地混合在一起。在某些情况下,它实际上更适合在后面的位置类线性化,因此可以使用抽象类。请参阅constraining class linearization (mixin order) in Scala

编辑2(2018):

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

从Scala 2.12开始,特征compile to Java interfaces,所以要求已经放松了一点。如果特征执行以下任何操作,则其子类仍需要重新编译:

  
      
  • 定义字段(valvar,但常量可以 - final val没有结果类型)
  •   
  • 致电super
  •   
  • 正文中的初始化语句
  •   
  • 扩展课程
  •   
  • 依靠线性化来找到正确的supertrait中的实现
  •   

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

答案 2 :(得分:75)

无论价值多少,Odersky等人Programming in Scala建议,当你怀疑时,你会使用特征。如果需要,您可以随后将它们更改为抽象类。

答案 3 :(得分:19)

除了你不能直接扩展多个抽象类,但你可以将多个特征混合到一个类中之外,值得一提的是特征是可堆叠的,因为特征中的超级调用是动态绑定的(它指的是一个类或者特征在当前之前混合了。)

来自托马斯在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

答案 4 :(得分:9)

扩展抽象类时,这表明子类类似。我认为这不是必须使用特征的情况。

答案 5 :(得分:7)

Programming Scala中,作者说抽象类使得经典的面向对象成为“一个”关系,而特征是一种组合的scala方式。

答案 6 :(得分:5)

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

答案 7 :(得分:1)

  1. 一个类可以从多个traits继承,但只能从一个抽象类继承。
  2. 抽象类可以包含构造函数参数以及类型参数。特征只能有类型参数。例如,你不能说trait t(i:Int){}; i参数是非法的。
  3. 抽象类可与Java完全互操作。你可以从没有任何包装器的Java代码中调用它们。只有当特征不包含任何实现代码时,特征才能完全互操作。