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

发布于 2024-08-16 18:37:22 字数 52 浏览 5 评论 0原文

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

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

梦里泪两行 2024-08-23 18:37:22

我可以想到两个区别:

  1. 抽象类可以有构造函数参数和类型参数。特征只能有类型参数。有人讨论说,将来甚至特征也可以有构造函数参数,
  2. 抽象类可以与 Java 完全互操作。您可以从 Java 代码中调用它们,无需任何包装器。仅当特征不包含任何实现代码时,它们才完全可互操作

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
時窥 2024-08-23 18:37:22

《Scala 编程》中有一节名为 “要特质,还是不要特质?” 这解决了这个问题。由于第一版可以在线获取,我希望可以在这里引用整个内容。 (任何认真的 Scala 程序员都应该购买这本书):

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

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

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

如果您想在 Java 代码中继承它,请使用抽象类。
由于带有代码的特征没有紧密的 Java 类似物,因此它往往是
从 Java 类中的特征继承是很困难的。继承自
与此同时,Scala 类与继承 Java 类完全相同。
作为一个例外,只有抽象成员的 Scala 特征会转换为
直接到Java接口,所以你应该随意定义这样的
特征,即使您希望 Java 代码继承它。参见第 29 章
了解有关 Java 和 Scala 一起使用的更多信息。

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

如果效率非常重要,则倾向于使用类。大多数爪哇
运行时使类成员的虚拟方法调用更快
操作而不是接口方法调用。特征被编译为
接口,因此可能会付出轻微的性能开销。
然而,只有当你知道这个特质时,你才应该做出这个选择。
有问题构成性能瓶颈并有证据
使用类实际上可以解决问题。

如果您仍然不知道,考虑完上述内容后,请从
使其成为一种特质。您以后可以随时更改它,一般来说
使用特征可以保留更多选择。

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

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

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

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

编辑 1 (2013):

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

编辑 2 (2018):

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

从 Scala 2.12 开始,traits 编译为 Java 接口,所以要求放宽了一些。如果该特征执行以下任何操作,则其子类仍然需要重新编译:

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

以查找正确超级特征中的实现,但如果该特征没有,您现在可以在不破坏二进制兼容性的情况下更新它。

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):

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.

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

Another difference is the treatment of 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.

See the rest of Chapter 12 for more details.

Edit 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.

Edit 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.

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:

  • 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

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

梦途 2024-08-23 18:37:22

无论其价值如何,Odersky 等人的 Scala 编程 建议,当您怀疑,你使用特质。如果需要的话,您可以稍后将它们更改为抽象类。

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

A君 2024-08-23 18:37:22

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

来自托马斯在抽象类和特征之间的差异中的回答:

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

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:

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
倾城花音 2024-08-23 18:37:22

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

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.

独闯女儿国 2024-08-23 18:37:22

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

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

蝶舞 2024-08-23 18:37:22

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

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.

岛徒 2024-08-23 18:37:22
  1. 一个类可以继承多个特征,但只能继承一个抽象类。
  2. 抽象类可以具有构造函数参数和类型参数。特征只能有类型参数。例如,你不能说 Trait t(i: Int) { }; i 参数非法。
  3. 抽象类与 Java 完全可互操作。您可以从 Java 代码中调用它们,无需任何包装器。仅当特征不包含任何实现代码时,它们才完全可互操作。
  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.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文