Trait
A trait is similar to an abstract class: it can declare several abstract or concrete members and can be extended. It cannot be instantiated. The difference is that a given class can only extend one abstract class, however, it can mixin one to many traits. Also, a trait cannot have constructor arguments.
For instance, we can declare several traits, each declaring different abstract methods, and mixin them all in the Rectangle class:
trait Description {
def description: String
}
trait Coordinates extends Description {
def x: Int
def y: Int
def description: String =
"Coordinates (" + x + ", " + y + ")"
}
trait Area {
def area: Double
}
class Rectangle(val x: Int,
val y: Int,
val width: Int,
val height: Int)
extends Coordinates with Description with Area {
val area: Double = width * height
override def description: String =
super.description + " - Rectangle " + width + " * " + height
}
val rect = new Rectangle(x = 0, y = 3, width = 3, height = 2)
rect.description
The following string gets printed when evaluating rect.description:
res0: String = Coordinates (0, 3) - Rectangle 3 * 2
The class Rectangle mixes in the traits Coordinates, Description, and Area. We need to use the keyword extends before trait or class, and the keyword with for all subsequent traits.
Notice that the Coordinates trait also mixes the Description trait, and provides a default implementation. As we did when we had a Shape class, we override this implementation in Rectangle, and we can still call super.description to refer to the implementation of description in the trait Coordinates.
Another interesting point is that you can implement an abstract method with val – in trait Area, we defined def area: Double, and implemented it in Rectangle using val area: Double. It is a good practice to define abstract members with def. This way, the implementer of the trait can decide whether to define it by using a method or a variable.