Using Scala Traits Like Abstract Classes

In the previous lesson I showed how to use Scala traits like Java interfaces, but they have much more functionality than that. You can also add real, working methods to them and use them like abstract classes, or more accurately, as mixins.

A first example

To demonstrate this, here’s a Scala trait that has a concrete method named speak, and an abstract method named comeToMaster:

trait Pet {
    def speak { println("Yo") }   // concrete implementation of a speak method
    def comeToMaster(): Unit      // abstract
}

When a class extends a trait, each defined method must be implemented, so here’s a class that extends Pet and defines comeToMaster:

class Dog(name: String) extends Pet {
    def comeToMaster(): Unit = { println("Woo-hoo, I'm coming!") }
}

Unless you want to override speak, there’s no need to redefine it, so this is a perfectly complete Scala class. Now you can create a new Dog like this:

val d = new Dog("Zeus")

Then you can call speak and comeToMaster. This is what it looks like in the REPL:

scala> val d = new Dog("Zeus")
d: Dog = Dog@4136cb25

scala> d.speak
Yo

scala> d.comeToMaster
Woo-hoo, I'm coming!

Overriding an implemented method

A class can also override a method that’s defined in a trait. Here’s an example:

class Cat extends Pet {
    // override 'speak'
    override def speak(): Unit = { println("meow") }
    def comeToMaster(): Unit = { println("That's not gonna happen.") }
}

The REPL shows how this works:

scala> val c = new Cat
c: Cat = Cat@1953f27f

scala> c.speak
meow

scala> c.comeToMaster
That's not gonna happen.

Mixing in multiple traits that have behaviors

A great thing about Scala traits is that you can mix multiple traits that have behaviors into classes. For example, here’s a combination of traits, one of which defines an abstract method, and the others that define concrete method implementations:

trait Speaker {
    def speak(): String   //abstract
}

trait TailWagger {
    def startTail(): Unit = { println("tail is wagging") }
    def stopTail(): Unit = { println("tail is stopped") }
}

trait Runner {
    def startRunning(): Unit = { println("I'm running") }
    def stopRunning(): Unit = { println("Stopped running") }
}

Now you can create a Dog class that extends all of those traits while providing behavior for the speak method:

class Dog(name: String) extends Speaker with TailWagger with Runner {
    def speak(): String = "Woof!"
}

And here’s a cat class

class Cat extends Speaker with TailWagger with Runner {
    def speak(): String = "Meow"
    override def startRunning(): Unit = { println("Yeah ... I don't run") }
    override def stopRunning(): Unit = { println("No need to stop") }
}

The REPL shows that this all works like you’d expect it to work. First, a Dog:

scala> d.speak
res0: String = Woof!

scala> d.start
startRunning   startTail

scala> d.startRunning
I'm running

scala> d.startTail
tail is wagging

Then a Cat:

scala> val c = new Cat
c: Cat = Cat@1b252afa

scala> c.speak
res1: String = Meow

scala> c.startRunning
Yeah ... I don't run

scala> c.startTail
tail is wagging

Mixing traits in on the fly

As a last note, a very interesting thing you can do with traits that have concrete methods is that you can mix them in on the fly. For example, given these traits:

trait TailWagger {
    def startTail(): Unit = { println("tail is wagging") }
    def stopTail(): Unit = { println("tail is stopped") }
}

trait Runner {
    def startRunning(): Unit = { println("I'm running") }
    def stopRunning(): Unit = { println("Stopped running") }
}

and this Dog class:

class Dog(name: String)

you can create a Dog instance that mixes in those traits when you create a Dog instance:

val d = new Dog("Fido") with TailWagger with Runner
                        ---------------------------

Once again the REPL shows that this works:

scala> val d = new Dog("Fido") with TailWagger with Runner 
d: Dog with TailWagger with Runner = $anon$1@50c8d274

scala> d.startTail
tail is wagging

scala> d.startRunning
I'm running

This example works because all of the methods in the TailWagger and Runner traits are defined (they’re not abstract).

See also

There are many more things you can do with Scala traits. For more details and examples, please see the Scala Cookbook.

results matching ""

    No results matching ""