关于scala:使用抽象类而不是traits有什么好处?

What is the advantage of using abstract classes instead of traits?

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


我能想到两个不同点

  • 抽象类可以有构造函数参数和类型参数。特性只能有类型参数。有人讨论过,将来甚至特性都可以有构造器参数。
  • 抽象类与Java完全可互操作。您可以用Java代码调用它们,而不需要任何包装器。只有当特性不包含任何实现代码时,它们才完全可互操作。

  • 在scala的编程中有一个部分叫做"去特征,还是不去特征?"它解决了这个问题。因为第一版的ED是在线的,所以我希望可以在这里引用全部内容。(任何严肃的scala程序员都应该买这本书):

    Whenever you implement a reusable collection of behavior, you will
    have to decide whether you want to use a trait or an abstract class.
    There is no firm rule, but this section contains a few guidelines to
    consider.

    If the behavior will not be reused, then make it a concrete class. It
    is not reusable behavior after all.

    If it might be reused in multiple, unrelated classes, make it a trait.
    Only traits can be mixed into different parts of the class hierarchy.

    If you want to inherit from it in Java code, use an abstract class.
    Since traits with code do not have a close Java analog, it tends to be
    awkward to inherit from a trait in a Java class. Inheriting from a
    Scala class, meanwhile, is exactly like inheriting from a Java class.
    As one exception, a Scala trait with only abstract members translates
    directly to a Java interface, so you should feel free to define such
    traits even if you expect Java code to inherit from it. See Chapter 29
    for more information on working with Java and Scala together.

    If you plan to distribute it in compiled form, and you expect outside
    groups to write classes inheriting from it, you might lean towards
    using an abstract class. The issue is that when a trait gains or loses
    a member, any classes that inherit from it must be recompiled, even if
    they have not changed. If outside clients will only call into the
    behavior, instead of inheriting from it, then using a trait is fine.

    If efficiency is very important, lean towards using a class. Most Java
    runtimes make a virtual method invocation of a class member a faster
    operation than an interface method invocation. Traits get compiled to
    interfaces and therefore may pay a slight performance overhead.
    However, you should make this choice only if you know that the trait
    in question constitutes a performance bottleneck and have evidence
    that using a class instead actually solves the problem.

    If you still do not know, after considering the above, then start by
    making it as a trait. You can always change it later, and in general
    using a trait keeps more options open.

    正如@mushtaq ahmed所提到的,特性不能有任何参数传递给类的主构造函数。

    另一个区别是治疗super

    The other difference between classes and traits is that whereas in classes, super calls are statically bound, in traits, they are dynamically bound. If you write super.toString in a class, you know exactly which method implementation will be invoked. When you write the same thing in a trait, however, the method implementation to invoke for the super call is undefined when you define the trait.

    更多细节见第12章的其余部分。

    编辑1(2013):

    抽象类的行为方式与特征有细微的区别。线性化规则之一是它保留了类的继承层次结构,这种继承层次结构倾向于将抽象类推送到链的后面,而特性可以很好地混合在一起。在某些情况下,处于类线性化的后一个位置实际上更可取,因此抽象类可以用于此目的。请参见scala中的约束类线性化(混合顺序)。

    编辑2(2018):

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

    至于Scala 2.12,特性被编译成Java接口,因此需求放宽了一点。如果特性有以下任何一种,它的子类仍然需要重新编译:

    • defining fields (val or var, but a constant is ok – final val without result type)
    • calling super
    • initializer statements in the body
    • extending a class
    • relying on linearization to find implementations in the right supertrait

    但是如果特性不存在,那么您现在可以在不破坏二进制兼容性的情况下更新它。


    不管它有什么价值,奥德斯基等人在scala中的编程建议,当你怀疑的时候,你应该使用特征。如果需要的话,可以在以后将它们更改为抽象类。


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

    从托马斯对抽象阶级与特质差异的回答来看:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    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

    当扩展抽象类时,这表明子类是类似的。我认为,在使用特性时,这种情况是不必要的。


    在编写scala的过程中,作者说抽象类形成了经典的面向对象的"is-a"关系,而特征是scala的组合方式。


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


  • 一个类可以继承多个特性,但只能继承一个抽象类。
  • 抽象类可以有构造函数参数和类型参数。特性只能有类型参数。例如,您不能说trait t(i:int)i参数是非法的。
  • 抽象类与Java完全可互操作。您可以用Java代码调用它们,而不需要任何包装器。只有当特性不包含任何实现代码时,它们才是完全可互操作的。