They are identical in terms of performance. If you write a test like this:
object Traits {
trait A { def a = "apple" }
trait B extends A { def b = "blueberry" }
trait C1 extends B { def c = "cherry" }
trait C2 extends A { def c = "chard" }
class Dessert extends B with C1 { }
class Salad extends B with C2 { }
}
and look at the bytecode for Dessert
and Salad
you see
public Traits$Dessert();
Code:
0: aload_0
1: invokespecial #29; //Method java/lang/Object."<init>":()V
4: aload_0
5: invokestatic #33; //Method Traits$A$class.$init$:(LTraits$A;)V
8: aload_0
9: invokestatic #36; //Method Traits$B$class.$init$:(LTraits$B;)V
12: aload_0
13: invokestatic #39; //Method Traits$C1$class.$init$:(LTraits$C1;)V
16: return
public Traits$Salad();
Code:
0: aload_0
1: invokespecial #29; //Method java/lang/Object."<init>":()V
4: aload_0
5: invokestatic #33; //Method Traits$A$class.$init$:(LTraits$A;)V
8: aload_0
9: invokestatic #36; //Method Traits$B$class.$init$:(LTraits$B;)V
12: aload_0
13: invokestatic #39; //Method Traits$C2$class.$init$:(LTraits$C2;)V
16: return
If you then go and look at the initializers for C1
and C2
, they're both empty. If you look at the method call for c
, again, it's a reference to the one defined either in C1
or C2
.
This happens because of the way layered traits are interpreted. You can think of them as a stack: each time you add a "with", the whole inheritance hierarchy is pushed onto the stack except that anything already there is not added again. So it doesn't matter whether C2
has B
or not, since the class Salad
already picked up B
because it extends B
.