Они идентичны с точки зрения производительности. Если вы напишите такой тест:
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 { }
}
и посмотрите на байт-код для Dessert
и Salad
, который вы видите
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
Если вы затем посмотрите на инициализаторы для C1
и C2
, они оба пусты. Если вы посмотрите на вызов метода для c
, опять же, это ссылка на вызов, определенный в C1
или C2
.
Это происходит из-за способа интерпретации слоистых признаков. Вы можете думать о них как о стеке: каждый раз, когда вы добавляете «с», вся иерархия наследования помещается в стек за исключением , что все, что уже есть, не добавляется снова. Поэтому не имеет значения, имеет ли C2
значение B
или нет, поскольку класс Salad
уже поднял B
, поскольку он расширяется B
.