What an interface is
An interface describes what a class can do without saying how it does it. A Truck, a Robot and a Drone are different kinds of things, but all of them can move. A Movable contract that lists move() lets code treat them alike without forcing them into one family tree.
Inheritance models "is a": a Dog is an Animal. An interface models "can do": a Dog can speak. A class has one parent class but may adopt many such contracts.
Traits are Scala's interfaces
Scala has no interface keyword. Its tool for this job is the trait:
trait Speaker {
def speak(): Unit
}
As in an abstract class, a def without a body is abstract. A class adopts the trait with extends, and it then has to implement every abstract member:
class Dog extends Speaker {
override def speak(): Unit = {
println("Woof")
}
}
Where Java uses two keywords, extends for a parent class and implements for an interface, Scala uses extends for the first parent, whether it is a class or a trait. Scala has no implements keyword.
If Dog leaves out speak, the compiler reports class Dog needs to be abstract, since def speak(): Unit in trait Speaker is not defined.
Several traits at once
The first parent comes after extends. Every further trait is added with with:
trait Speaker { def speak(): Unit }
trait Mover { def move(): Unit = println("moving") }
class Dog extends Speaker with Mover {
override def speak(): Unit = println("Woof")
}
class Base
class Child extends Base with Speaker {
def speak(): Unit = println("child")
}
Dog().speak() prints Woof, and Dog().move() prints moving. Child shows a class with a real parent class that also adopts a trait. This is the "many contracts" part: one class, many capabilities.
The Mover trait also shows that a Scala trait may carry code. move() has a body, so every class that adopts Mover gets it for free, and may override it. A trait with only abstract members is the pure contract the lesson describes. A trait with concrete members goes further than a classic interface.
Trait versus abstract class
| Abstract class | Trait | |
|---|---|---|
| Bodies for some methods | Yes | Yes |
| A class can extend several | No, one parent class | Yes, joined with with |
| Usual role | "is a" | "can do" |
As a rule of thumb, reach for a trait when you are describing a capability that unrelated classes can share, and for an abstract class when the classes are variants of one thing and share state.
Programming against the contract
Because Dog adopts Speaker, a Dog can be stored in a variable of type Speaker. Code that holds a Speaker knows only that it has speak(), and the call reaches the real object's implementation:
val rex: Speaker = Dog()
rex.speak() // Woof
You can't create a trait instance with Speaker(), which fails with Not found: Speaker. You always create a class that adopts it. Scala 3 does offer an anonymous class when you need a one-off implementation:
val s = new Speaker { def speak(): Unit = println("anon") }
s.speak() // anon
The exercise, step by step
The starter code declares the Speaker trait and a mainFunction that already builds a Dog and calls speak() on it as a Speaker. It needs a Dog.
- Declare
Dog. Writeclass Dog extends Speakerbelow the trait. The trait is adopted withextends. - Implement
speak. Addoverride def speak(): Unitand make its body printWoof. For an abstract memberoverrideis optional, but writing it makes the compiler check that the signature really matches a member of the trait.
The solution in Scala
trait Speaker {
def speak(): Unit
}
class Dog extends Speaker {
override def speak(): Unit = {
println("Woof")
}
}
class Solution {
def mainFunction(): Unit = {
val rex: Speaker = Dog()
rex.speak()
}
}
The parts to notice are the annotation val rex: Speaker, which hides the fact that the object is a Dog, and the call to Dog() without new, which Scala 3 allows for concrete classes.
Common mistakes
- Writing
implements. It isn't a Scala keyword; useextends. - Using
withfirst. The first parent followsextends;withonly adds further traits. - Changing the signature. A method named
speakwith different parameters doesn't implement the trait'sspeak(). - Forgetting
Unitor the body. The method has to be a full definition with=and a body.
Key takeaways
- A trait is Scala's interface: it declares what a class can do.
- A class adopts a trait with
extends, and more traits withwith. - The class must implement every abstract member of its traits.
- Traits may also contain concrete members, which adopting classes inherit.
