Daniel Ciocîrlan
3 min read •
Share on:
This is for the beginner Scala programmer who is just getting started with Scala’s inheritance model. If you’re trying to figure out which is the best way to create OO-style type hierarchies and abstract classes seem too similar to traits, this article is for you.
This short article will compare abstract classes and traits as means of inheritance.
Scala is particularly permissive when it comes to inheritance options. Of course, Scala has a single-class inheritance model, where one can extend a single class. Much like Java, Scala classes can inherit from multiple traits (the equivalent of Java interfaces).
Scala also has the concept of an abstract class, where one can
abstract class Person {
// the abstract keyword is not necessary in the field/method definition
val canDrive: Boolean
def discussWith(another: Person): String
}
val bob = new Person // error - not all members implemented
An abstract class may or may not have either abstract (unimplemented) or non-abstract (implemented) methods.
However, traits also share most of the same capabilities:
So while teaching Scala at Rock the JVM, I often come across this popular question: how are traits and abstract classes really different?
There is little practical difference between an abstract class and a trait if you extend a single one. If you look at the example below:
class Adult(val name: String, hasDrivingLicence: Boolean) extends Person {
override def toString: String = name
override val canDrive: Boolean = hasDrivingLicence
override def discussWith(another: Person): String = "Indeed, ${other}, Kant was indeed revolutionary for his time..."
}
the code is identical, regardless of whether Person was a trait or an abstract class. So from this point of view, a trait and an abstract class are identical.
When we move towards multiple inheritance, things start to look different. One of the fundamental differences between a trait and an abstract class is that multiple traits may be used in the inheritance definition of a class, whereas a single abstract class can be extended. Since traits can have non-abstract methods as well, a good (and natural) follow-up question might be: “but what happens if a class inherits from two traits with the same method implemented in different ways?”, which resembles the famous diamond problem. Scala solves it with a dedicated mechanism called linearization, which we will discuss in another article.
A second, more practical difference between a trait and an abstract class is that an abstract class can take constructor arguments:
abstract class Person(name: String, age: Int)
whereas traits cannot:
trait Person(name: String, age: Int) // error
That is, at least until Scala 3 comes out - Scala 3 will also allow constructor arguments for traits.
When I speak in a live training about the above differences between traits and abstract classes, my audience is usually not impressed - it seems the overlap is so large that there might be no purpose to two different concepts.
However, I always end up with the following reasoning: for good practice, it is always a good idea to make code easy to read, understand and reason about. To this aim, we generally represent “things” as classes, and “behaviors” as traits. This conceptual difference is directly mapped in the language: a “thing” (class) can be a specialized version of another “thing” (also a class), but might incorporate multiple “behaviors” (traits).
Thank you for reading this article. I hope you found it useful.
Share on: