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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1991042/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-22 01:49:15  来源:igfitidea点击:

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

scalatraits

提问by Ralf

What is the advantage of using an abstract class instead of a trait (apart from performance)? It seems like abstract classes can be replaced by traits in most cases.

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

回答by Mushtaq Ahmed

I can think of two differences

我能想到两个不同点

  1. Abstract classes can have constructor parameters as well as type parameters. Traits can have only type parameters. There was some discussion that in future even traits can have constructor parameters
  2. Abstract classes are fully interoperable with Java. You can call them from Java code without any wrappers. Traits are fully interoperable only if they do not contain any implementation code
  1. 抽象类可以有构造函数参数和类型参数。特征只能有类型参数。有一些讨论,未来甚至 trait 也可以有构造函数参数
  2. 抽象类可以与 Java 完全互操作。您可以在没有任何包装器的情况下从 Java 代码中调用它们。仅当特征不包含任何实现代码时,特征才完全可互操作

回答by Eugene Yokota

There's a section in Programming in Scala called "To trait, or not to trait?"which addresses this question. Since the 1st ed is available online, I'm hoping it's OK to quote the whole thing here. (Any serious Scala programmer should buy the book):

在 Scala 编程中有一节叫做“要特征,还是不要特征?” 它解决了这个问题。由于第 1 版可在线获取,我希望在这里引用整个内容是可以的。(任何认真的 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.

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

如果行为不会被重用,则将其设为具体类。毕竟它不是可重用的行为。

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

如果要在 Java 代码中继承它,请使用抽象类。由于代码中的 trait 没有与 Java 相似的类似物,因此从 Java 类中的 trait 继承往往会很尴尬。同时,从 Scala 类继承与从 Java 类继承完全一样。作为一个例外,只有抽象成员的 Scala 特征直接转换为 Java 接口,因此即使您希望 Java 代码继承自它,您也应该随意定义此类特征。有关同时使用 Java 和 Scala 的更多信息,请参见第 29 章。

如果您计划以编译形式分发它,并且您希望外部组编写从它继承的类,您可能倾向于使用抽象类。问题是,当一个 trait 获得或失去一个成员时,任何继承自它的类都必须重新编译,即使它们没有改变。如果外部客户端只会调用行为,而不是从它继承,那么使用特征就可以了。

如果效率非常重要,则倾向于使用类。大多数 Java 运行时使类成员的虚拟方法调用比接口方法调用更快。Traits 被编译为接口,因此可能会付出轻微的性能开销。但是,只有当您知道所讨论的特征构成性能瓶颈并且有证据表明使用类实际上可以解决问题时,才应该做出此选择。

如果您仍然不知道,在考虑了上述内容后,请先将其作为特征。您可以随时更改它,并且通常使用 trait 可以保持更多选项打开。

As @Mushtaq Ahmed mentioned, a trait cannot have any parameters passed to the primary constructor of a class.

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

Another difference is the treatment of super.

另一个区别是super.

The other difference between classes and traits is that whereas in classes, supercalls are statically bound, in traits, they are dynamically bound. If you write super.toStringin 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.

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

See the rest of Chapter 12for more details.

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

Edit 1 (2013):

编辑 1 (2013):

There is a subtle difference in the way abstract classes behaves compared to traits. One of the linearization rules is that it preserves the inheritance hierarchy of the classes, which tends to push abstract classes later in the chain while traits can happily be mixed in. In certain circumstances, it's actually preferable to be in latter position of the class linearization, so abstract classes could be used for that. See constraining class linearization (mixin order) in Scala.

与特征相比,抽象类的行为方式存在细微差别。线性化规则之一是它保留了类的继承层次结构,这倾向于将抽象类推到链的后面,而特征可以愉快地混入。在某些情况下,实际上最好在类线性化的后面位置,因此可以使用抽象类。请参阅Scala 中的约束类线性化(混合顺序)

Edit 2 (2018):

编辑 2(2018 年):

As of Scala 2.12, trait's binary compatibility behavior has changed. Prior to 2.12, adding or removing a member to the trait required recompilation of all classes that inherit the trait, even if the classes have not changed. This is due to the way traits were encoded in JVM.

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

As of Scala 2.12, traits compile to Java interfaces, so the requirement has relaxed a bit. If the trait does any of the following, its subclasses still require recompilation:

从 Scala 2.12 开始,traits编译为 Java 接口,所以要求放宽了一点。如果 trait 做了以下任何一项,它的子类仍然需要重新编译:

  • defining fields (valor var, but a constant is ok – final valwithout result type)
  • calling super
  • initializer statements in the body
  • extending a class
  • relying on linearization to find implementations in the right supertrait
  • 定义字段(valvar,但常量是可以的——final val没有结果类型)
  • 打电话 super
  • 正文中的初始化语句
  • 扩展一个类
  • 依靠线性化在正确的超特征中找到实现

But if the trait does not, you can now update it without breaking binary compatibility.

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

回答by Daniel C. Sobral

For whatever it is worth, Odersky et al's Programming in Scalarecommends that, when you doubt, you use traits. You can always change them into abstract classes later on if needed.

不管它的价值如何,Odersky 等人的Scala 编程建议,当您有疑问时,请使用特征。如果需要,您可以随时将它们更改为抽象类。

回答by Nemanja Boric

Other than the fact that you cannot directly extend multiple abstract classes, but you can mixin multiple traits into a class, it's worth mentioning that traits are stackable, since super calls in a trait are dynamically bound (it is referring a class or trait mixed before current one).

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

From Thomas's answer in Difference between Abstract Class and Trait:

来自 Thomas 在《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@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@188c838
scala> yx.a
X
Y
res1: Int = 1

回答by peter p

When extending an abstract class, this shows that the subclass is of a similar kind. This is not neccessarily the case when using traits, I think.

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

回答by Marten

In Programming Scalathe authors say that abstract classes make a classical object oriented "is-a" relationship while traits are a scala-way of composition.

编程 Scala 中,作者说抽象类构成了经典的面向对象的“is-a”关系,而特征是一种 Scala 组合方式。

回答by Dario

Abstract classes can contain behaviour - They can parameterized with constructor args (which traits can't) and represent a working entity. Traits instead just represent a single feature, an interface of one functionality.

抽象类可以包含行为——它们可以用构造函数参数化(特征不能)并代表一个工作实体。相反,Traits 仅代表一个特性,一个功能的接口。

回答by pavan.vn101

  1. A class can inherit from multiple traits but only one abstract class.
  2. Abstract classes can have constructor parameters as well as type parameters. Traits can have only type parameters. For example, you can't say trait t(i: Int) { }; the i parameter is illegal.
  3. Abstract classes are fully interoperable with Java. You can call them from Java code without any wrappers. Traits are fully interoperable only if they do not contain any implementation code.
  1. 一个类可以继承多个特征,但只能继承一个抽象类。
  2. 抽象类可以有构造函数参数和类型参数。特征只能有类型参数。例如,你不能说 trait t(i: Int) { }; i 参数是非法的。
  3. 抽象类可以与 Java 完全互操作。您可以在没有任何包装器的情况下从 Java 代码中调用它们。仅当特征不包含任何实现代码时,特征才完全可互操作。