ChatGPT解决这个技术问题 Extra ChatGPT

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

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.


M
Mushtaq Ahmed

I can think of two differences

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


Very important addendum: A class can inherit from multiple traits but only one abstract class. I think this should be the first question a developer asks when considering which to use in almost all cases.
lifesaver: "Traits are fully interoperable only if they do not contain any implementation code"
abstract - when collective behaviors defining or leading to a object (branch of object) but still not yet composed to be as (ready) object. Traits, when you need to induct capabilities i.e. capabilities never originate with creation of object, it evolves or required when an object comes out from isolation and has to communicate.
The second difference doesn't exist in Java8, think.
Per Scala 2.12, a trait compiles to a Java 8 interface - scala-lang.org/news/2.12.0#traits-compile-to-interfaces.
Y
Yawar

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.


If outside clients will only call into the behavior, instead of inheriting from it, then using a trait is fine - Could someone explain what is the difference here? extends vs with?
@0fnt His distinction is not about extends vs with. What he's saying is that if you only mix in the trait within the same compilation, the binary compatibility issues do not apply. However, if your API is designed to allow users to mix in the trait themselves, then you will have to worry about binary compatibility.
@0fnt: There is absolutely no semantic difference between extends and with. It is purely syntactical. If you inherit from multiple templates, the first gets extend, all the others get with, that's it. Think of with as a comma: class Foo extends Bar, Baz, Qux.
what does this mean in scala Trait can be added to an object instance. Abstract class cannot be added to an object instance.
D
Daniel C. Sobral

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.


C
Community

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

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


Does this have any practical implications, or does it only make the code easier to understand?
M
Marten

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.


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


Hope you are not implying that traits cannot contain behavior. Both can contain implementation code.
@Mitch Blevins: Of course not. They can contain code, but when you define trait Enumerable with lots of helper functions, I wouldn't call them behaviour but just functionality connected with one feature.
@Dario I see "behavior" and "functionality" as being synonyms, so I find your answer to be very confusing.
p
pavan.vn101

A class can inherit from multiple traits but only one abstract class. 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. 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.