Published 2014-02-19.
Last modified 2014-12-16.
Time to read: 3 minutes.
Traits can be ‘stacked’, or mixed together, and this lecture describes how stacked traits work. This lecture also shows how traits can also be used to modify the behavior of any JVM class.
courseNotes/src/main/scala/Traits2.scala.
Stackable Traits
In Scala, just as in Java, super for subclasses is determined statically.
Scala traits are much more flexible, and this lecture will explore two ways to work with Scala traits that are beyond anything that Java can do.
Methods defined in traits can override concrete methods defined in classes or traits that they extend.
To do this, the trait’s overriding methods must be decorated with abstract override.
This is possible because for traits, super is dynamically determined according to the order in which the trait was mixed in.
When a method is called on a trait, the trait furthest right in the mix which has that method provides the implementation.
If this method calls super then it invokes the method on the trait on its left, and so on.
In the following example, the Color trait is not abstract, so the Red, White and Blue traits can
superimpose their two abstract override methods: color and paint on concrete implementations.
The paint methods are written in such a way that you can see how Scala applies the overrides.
object WhichTrait extends App {
trait Color {
def color: String = "Unknown"
def paint(highlight: String): String = s"$color with $highlight highlights"
}
trait Red extends Color {
override val color: String = "red"
override def paint(highlight: String): String = super.paint(s"pink and $highlight")
}
trait White extends Color {
override val color: String = "white"
override def paint(highlight: String): String = super.paint(s"light yellow and $highlight")
}
trait Blue extends Color {
override val color: String = "blue"
override def paint(highlight: String): String = super.paint(s"bluegreen and $highlight")
}
val red = new Red{}
println(s"""red.paint=${red.paint("polkadot")}""")
val redWhite = new Red with White
println(s"""redWhite.paint=${redWhite.paint("polkadot")}""")
val redWhiteBlue = new Red with White with Blue
println(s"""redWhiteBlue.paint=${redWhiteBlue.paint("polkadot")}""")
class Concrete extends Red with White with Blue
val concrete = new Concrete
println(s"""concrete.paint=${concrete.paint("polkadot")}""")
}
You can run this program by typing.
$ scala> sbt "runMain WhichTrait"
Output is.
red.paint=red with pink and polkadot highlights redWhite.paint=white with pink and light yellow and polkadot highlights redWhiteBlue.paint=blue with pink and light yellow and bluegreen and polkadot highlights concrete.paint=blue with pink and light yellow and bluegreen and polkadot highlights
The red variable contains an instance of an anonymous subclass of the Red trait.
Similarly, the redWhite variable contains an instance of an anonymous subclass of the Red and White traits
mixed together.
The redWhiteBlue variables contains an instance of an anonymous subclass of the Red, White and
Blue traits.
The concrete variable contains an instance of the Concrete class, which is also a mix of the Red,
White and Blue traits.
Although the behavior of redWhiteBlue is identical to that of concrete, there is only one class definition for all
Concrete instances.
In contrast, each time you write Red with White with Blue a new abstract class definition is created.
When creating the red variable by using the Red trait alone, we need to include an empty body to create an anonymous
subclass of Red, like this.
new Red{}
new Red with White
Notice the order of the colors that are printed out by redWhiteBlue.paint and concrete.paint.
- The invocation passes
polkadottoconcrete.paint. - The rightmost trait,
Blue, prependsbluegreenand invokessuper.paint. - The next rightmost trait,
White, prependslight yellowand invokessuper.paint. - The next rightmost trait,
Red, prependspinkand invokessuper.paint. - The
Colortrait, which is the superclass of theRedtrait, prepends the value ofcolor. Unlike thepaintmethods, thecolormethods in each trait do not callsuper, so that property is consistent for all traits, and it set by the right-most trait that was mixed in.
Abstract Override
The following example defines a trait that extends the mutable java.util.Set[String], which is a Java interface that uses generics.
Scala also supports generics, and we will cover that topic in the
Parametric Types lecture of the
Intermediate Scala course.
This code is somewhat ugly because Scala generics are not used.
The IgnoredCaseSet trait’s overriding add method converts any incoming Strings to lower case before
adding it to the set.
Other types of objects are merely added to the set without modification.
This code uses isInstanceOf to test if the runtime type of obj is a String, and asInstanceOf to
typecast obj to String if that is its runtime type.
Later in this course we will learn how to use the match keyword and then we won’t need to use isInstanceOf any more.
trait IgnoredCaseSet extends java.util.Set[Object] {
abstract override def add(obj: Object): Boolean =
if (obj.isInstanceOf[String]) super.add(obj.asInstanceOf[String].toLowerCase) else super.add(obj)
abstract override def contains(obj: Object): Boolean =
if (obj.isInstanceOf[String]) super.contains(obj.asInstanceOf[String].toLowerCase) else super.contains(obj)
}
class MySet extends java.util.HashSet[Object] with IgnoredCaseSet
Java HashSet implements Set, which is why we could subclass HashSet by mixing in IgnoredCaseSet
above.
Let’s create a MySet instance that contains Strings, and add mixed case Strings to it.
We will then use the trait’s overriding contains method to determine if the set contains the equivalent lower case
String.
scala> val set = new MySet() set: MySet = []
scala> %}set.add("One") res3: Boolean = true
scala> %}set.add("Two") res4: Boolean = true
scala> %}set.add("Three") res5: Boolean = true
scala> %}set.contains("hi there") res6: Boolean = false
scala> %}mySet.contains("two") res7: Boolean = true
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.