Method overriding
When overriding a method in a subclass, you may want to call the original implementation as well. This is achieved by prefixing the super keyword to the method name. The developer also has control to qualify the super keyword with a trait type, thus calling the method in the specific trait. We already saw an example of this earlier in the chapter, where we called super[A].hello(). In that example, we had mixins with the same methods; however, the methods themselves did not refer to super, but just defined their own implementations.
Let's see an example here, where we actually refer to the super class when overriding a method:
class MultiplierIdentity {
def identity: Int = 1
}
Let's now define two traits that respectively double and triple the identity in our original class:
trait DoubledMultiplierIdentity extends MultiplierIdentity {
override def identity: Int = 2 * super.identity
}
trait TripledMultiplierIdentity extends MultiplierIdentity {
override def identity: Int = 3 * super.identity
}
As we saw in some of the previous examples, the order in which we mix in the traits matters. We will provide three implementations, where we first mix in DoubledMultiplierIdentity and then TripledMultiplierIdentity. The first one will not override the identity method, which is equivalent to using the following super notation: super.identity. The other two will override the method and will refer to a specific parent:
// first Doubled, then Tripled
class ModifiedIdentity1 extends DoubledMultiplierIdentity with TripledMultiplierIdentity
class ModifiedIdentity2 extends DoubledMultiplierIdentity with TripledMultiplierIdentity {
override def identity: Int = super[DoubledMultiplierIdentity].identity
}
class ModifiedIdentity3 extends DoubledMultiplierIdentity with TripledMultiplierIdentity {
override def identity: Int = super[TripledMultiplierIdentity].identity
}
// first Doubled, then Tripled
Let's do the same thing as shown in the preceding code, but this time, we first mix in TripledMultiplierIdentity and then DoubledMultiplierIdentity. The implementations are similar to the preceding ones:
// first Tripled, then Doubled
class ModifiedIdentity4 extends TripledMultiplierIdentity with DoubledMultiplierIdentity
class ModifiedIdentity5 extends TripledMultiplierIdentity with DoubledMultiplierIdentity {
override def identity: Int = super[DoubledMultiplierIdentity].identity
}
class ModifiedIdentity6 extends TripledMultiplierIdentity with DoubledMultiplierIdentity {
override def identity: Int = super[TripledMultiplierIdentity].identity
}
// first Tripled, then Doubled
Finally, let's use our classes:
object ModifiedIdentityUser {
def main(args: Array[String]): Unit = {
val instance1 = new ModifiedIdentity1
val instance2 = new ModifiedIdentity2
val instance3 = new ModifiedIdentity3
val instance4 = new ModifiedIdentity4
val instance5 = new ModifiedIdentity5
val instance6 = new ModifiedIdentity6
System.out.println(s"Result 1: ${instance1.identity}")
System.out.println(s"Result 2: ${instance2.identity}")
System.out.println(s"Result 3: ${instance3.identity}")
System.out.println(s"Result 4: ${instance4.identity}")
System.out.println(s"Result 5: ${instance5.identity}")
System.out.println(s"Result 6: ${instance6.identity}")
}
}
The example shows a multiple inheritance hierarchy, where we can see a diamond relationship exactly as in the previous figure in which we explained what it means. We have all the possibilities here in terms of the order of mixing DoubledMultiplier and TripledMultiplier, as well as how we call the identity base method.
So, what would the output of this program be? One would expect that in the cases where we don't override the identity method, it would call the identity method of the right-most trait. Since in both the cases they call the super method of the class they extend, the results should be 2 and 3. Let's see this here:
Result 1: 6
Result 2: 2
Result 3: 6
Result 4: 6
Result 5: 6
Result 6: 3
The preceding output is rather unexpected. This is, however, how the Scala type system works. In the case of linearization, where we have a multiple inheritance, the calls to the same method are chained from right to left according to the order of the appearance of the traits in the class declaration. Note that if we did not use the super notation, we would have broken the chain, as can be seen in some of the preceding examples.