代码之家  ›  专栏  ›  技术社区  ›  Ralf

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

  •  346
  • Ralf  · 技术社区  · 16 年前

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

    8 回复  |  直到 7 年前
        1
  •  347
  •   Mushtaq Ahmed    16 年前

    我能想到两个不同点

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

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

    每当实现可重用的行为集合时,您将 必须决定是使用特征类还是抽象类。 没有固定的规则,但本节包含一些指导方针 考虑一下。

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

    如果它可以在多个不相关的类中重用 让它成为一种特质。 只有特性可以混合到类层次结构的不同部分中。

    如果您想从Java代码继承它 ,使用抽象类。 由于代码中的特性没有一个相近的Java模拟,所以它往往是 难以从Java类中的特性继承。继承自 同时,Scala类与Java类继承一样。 作为一个例外,只有抽象成员的scala特征可以翻译为 直接到Java接口,所以你可以自由地定义这样的 即使您希望Java代码继承它,也可以使用特性。见第29章 有关使用Java和Scala一起工作的更多信息。

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

    如果效率非常重要 倾向于使用一个类。最爪哇 运行时使类成员的虚拟方法调用更快 操作,而不是接口方法调用。特性被编译为 接口,因此可能会支付一点性能开销。 但是,只有当你知道 有疑问的是,这构成了性能瓶颈,并且有证据表明 使用类实际上解决了问题。

    如果你还不知道 ,考虑完以上内容后,从 使之成为一种特性。你可以以后再改,一般来说 使用一个特性可以保持更多的选择。

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

    另一个区别是 super .

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

    其余部分见 Chapter 12 了解更多详细信息。

    编辑1(2013):

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

    编辑2(2018):

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

    从scala 2.12开始,特征 compile to Java interfaces 所以这个要求已经放宽了一点。如果特性有以下任何一种,它的子类仍然需要重新编译:

    • 定义字段( val var 但是常数是可以的。 final val 无结果类型)
    • 打电话 超级的
    • 正文中的初始值设定项语句
    • 扩展类
    • 依靠线性化在正确的超特性中找到实现

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

        3
  •  75
  •   Daniel C. Sobral    16 年前

    不管它值多少钱,奥德斯基等人 Programming in Scala 建议,当你怀疑的时候,你使用特质。如果需要的话,可以在以后将它们更改为抽象类。

        4
  •  19
  •   Community Mohan Dere    9 年前

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

    从托马斯的回答 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
    
        5
  •  9
  •   peter p    16 年前

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

        6
  •  7
  •   Marten    13 年前

    Programming Scala 作者认为抽象类形成了一种经典的面向对象的“IS-A”关系,而特征是一种scala组合方式。

        7
  •  5
  •   Dario    16 年前

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

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