1. Structural sequence
Give the following categories and characteristics as examples
trait AA { println("A...") } trait BB extends AA { println("B....") } trait CC extends BB { println("C....") } trait DD extends BB { println("D....") } class EE { println("E...") } class FF extends EE with CC with DD { println("F....") } class KK extends EE { println("K....") }
1.1 declare classes and mix in characteristics at the same time
The construction order should be
① Call the superclass constructor of the current class
② The parent trait constructor of the first trait
③ First trait constructor
④ If the parent trait constructor of the second trait constructor has been executed, it will no longer be executed
⑤ The second trait constructor
⑥ Repeat steps 4 and 5 (if there is a third trait, the fourth trait)
⑦ Current class constructor
object TraitCreate { def main(args: Array[String]): Unit = { // Construct classes according to depth first // E -> A -> B -> C -> D -> F new FF } }
1.2 when building objects, dynamically mix characteristics
The construction order should be
① Call the superclass constructor of the current class
② Current class constructor
③ Parent trait constructor of the first trait constructor
④ The first trait constructor
⑤ If the parent trait constructor of the second trait constructor has been executed, it will no longer be executed
⑥ The second trait constructor
⑦ Repeat steps 5 and 6 (if there are 3 and 4 traits)
object TraitCreate { def main(args: Array[String]): Unit = { // Construct the class according to the depth first principle // E -> K -> A -> B -> C -> D new KK with CC with DD } }
1.3 summary
The difference between the construction order of the two ways of mixing traits is mainly reflected in the timing of the creation of the current class constructor
If the declared class is used to blend in the trait at the same time, the object has not been created when the trait is mixed in, so the current class constructor will not be created until the trait constructor is created.
If you dynamically mix in traits when building an object, it can be understood that when you mix in traits, the object has been created. Therefore, when you call the superclass constructor without mixing traits, the current class constructor has been created.
2. Order of calling super method
2.1 linearization in Scala
The method called by super in the trait depends on the * * * linearization * * * of the class and the traits mixed into the class.
When you instantiate a class with new, Scala will take out the class and all its inherited classes and characteristics and arrange them linearly. Then, when a super is called in a class, the called method is the last one in the chain (the one on the far right). If all methods except the last one call super, the final result is the superimposed behavior.
The exact order of linearization is described in Scala Language Specification. This description is a little complicated. It is explained in detail in the following website. Interested students can go on to study it:
Scala Language Specification - Classes and Objects
In general, that is, in any linearization, a class always precedes all its superclasses and mixed qualities.
but the main thing you need to know is that, in any linearization, a class is always linearized in front of all its superclasses and mixed in traits.
Therefore, when you write down a method that calls super, that method is definitely modifying the behavior of superclasses and mixing traits, not vice versa. It's a little around here. My understanding is to call the super method in the opposite direction of the constructor until super calls the method with the same name of the object at the deepest layer
Take the case in Programming in Scala written by Martin Odersky as an example
object Linearization { def main(args: Array[String]): Unit = { /** * Output results * Animal * Furry * HasLegs * FourLegged * Cat * Animal:Furry:HasLegs:FourLegged:Cat:miao miao~ */ val cat = new Cat cat.printInfo("miao miao~") } } class Animal { println("Animal") def printInfo(msg: String): Unit = println(s"Animal:${msg}") } trait Furry extends Animal { println("Furry") override def printInfo(msg: String): Unit = super.printInfo(s"Furry:${msg}") } trait HasLegs extends Animal { println("HasLegs") override def printInfo(msg: String): Unit = super.printInfo(s"HasLegs:${msg}") } trait FourLegged extends HasLegs { println("FourLegged") override def printInfo(msg: String): Unit = super.printInfo(s"FourLegged:${msg}") } class Cat extends Animal with Furry with FourLegged { println("Cat") override def printInfo(msg: String): Unit = super.printInfo(s"Cat:${msg}") }
The inheritance relationship and linearization of Cat classes are shown in the following figure
Cat linearization is calculated from back to front as follows. The last part of cat linearization is the linearization of its super class Animal. This linearization is copied directly without modification. Since Animal does not explicitly extend a super class and does not mix Any super characteristics, it extends from AnyRef by default, and AnyRef extends from Any. Thus, the linearization of Animal looks like this:
Animal --> AnyRef --> Any
The penultimate part of linearization is the linearization of the first blending (i.e. the Furry trait), but all classes that have appeared in Animal linearization will not appear again, and each class only appears once in Cat linearization. The result is:
Furry --> Animal --> AnyRef --> Any
Before this result, it is the linearization of FourLegged. Similarly, any class that has been copied in the superclass or the first blend will not appear again:
FourLeggerd --> HasLegs -- >Furry --> Animal --> AnyRef --> Any
Finally, the first class in Cat linearization:
Cat --> FourLeggerd --> HasLegs -- >Furry --> Animal --> AnyRef --> Any
The linearization process of Cat class is shown in the following table
2.2 case description
According to the above ideas, the following cases are sorted out
trait foo06 { println("foo06") def foo(msg: String): Unit = { println("I'm in foo06") println(s"foo06:${msg}") } } trait foo07 { println("foo07") def foo(msg: String): Unit = { println("I'm in foo07") println(s"foo07:${msg}") } } trait foo08 { println("foo08") def foo(msg: String): Unit = { println("I'm in foo08") println(s"foo08:${msg}") } } trait foo09 { println("foo09") def foo(msg: String): Unit = { println("I'm in foo09") println(s"foo09:${msg}") } } trait foo04 extends foo08 with foo06 with foo07 { println("foo04") override def foo(msg: String): Unit = { println("I'm in foo04") super.foo(s"foo04:${msg}") } } trait foo05 extends foo08 with foo09 { println("foo05") override def foo(msg: String): Unit = { println("I'm in foo05") super.foo(s"foo05:${msg}") } } trait foo01 extends foo04 with foo05 { println("foo01") override def foo(msg: String): Unit = { println("I'm in foo01") super.foo(s"foo01:${msg}") } } trait foo02 extends foo04 { println("foo02") override def foo(msg: String): Unit = { println("I'm in foo02") super.foo(s"foo02:${msg}") } } trait foo03 extends foo05 with foo04 with foo02 with foo07 { println("foo03") override def foo(msg: String): Unit = { println("I'm in foo03") super.foo(s"foo03:${msg}") } }
The inheritance relationship is shown in the figure below. Numbers represent the inheritance order. The larger the number, the more right it is. Red represents the inheritance relationship closest to the right.
Create the following classes and test them
object DiamondInherited { def main(args: Array[String]): Unit = { val fooApp = new FooApp fooApp.foo("fooApp") } } // foo01 --> foo02 --> foo03 class FooApp extends foo01 with foo02 with foo03 { override def foo(msg: String): Unit = super.foo(msg) }
give the result as follows
# Construct according to the principle of depth first foo08 foo06 foo07 foo04 foo09 foo05 foo01 foo02 foo03 # Linearize method call order I'm in foo03 I'm in foo02 I'm in foo01 I'm in foo05 I'm in foo09 # Final output foo09:foo05:foo01:foo02:foo03:fooApp
It can be seen that when calling with super, the method on the far right will be called, and the reconstructed method will be called in the opposite direction of construction until it is called to the lowest method.
Now adjust the inheritance order in FooApp
object DiamondInherited { def main(args: Array[String]): Unit = { val fooApp = new FooApp fooApp.foo("fooApp") } } // foo03 --> foo02 --> foo01 class FooApp extends foo01 with foo02 with foo03 { override def foo(msg: String): Unit = super.foo(msg) }
The results are as follows, in line with expectations
# Construct according to the principle of depth first foo08 foo09 foo05 foo06 foo07 foo04 foo02 foo03 foo01 # Linearize method call order I'm in foo01 I'm in foo03 I'm in foo02 I'm in foo04 I'm in foo07 # Final output foo07:foo04:foo02:foo03:foo01:fooApp
Of course, if a trait in the inheritance process does not override the method or call the super method, the super calling class will end when it reaches the trait
object DiamondInherited { def main(args: Array[String]): Unit = { val fooApp = new FooApp fooApp.foo("fooApp") } } // Comment out the super call in foo02, and other characteristics remain unchanged trait foo02 extends foo04 { println("foo02") override def foo(msg: String): Unit = { println("I'm in foo02") //super.foo(s"foo02:${msg}") } } class FooApp extends foo03 with foo02 with foo01 { override def foo(msg: String): Unit = super.foo(msg) }
The output results are as follows
# Construct according to the principle of depth first foo08 foo09 foo05 foo06 foo07 foo04 foo02 foo03 foo01 # Linearize method call order I'm in foo01 I'm in foo03 I'm in foo02 # Since the super method is not called in foo2, the call chain has ended so far, and the result cannot be output
2.3 specify the characteristics to be called
The above examples are all methods that determine the characteristics to be called through the integration relationship. If you need to specify which characteristics to use, you can directly specify them through super[clazz]
object TraitOverlying { def main(args: Array[String]): Unit = { val myFootBall = new MyFootBall // my ball is a foot-ball println(myFootBall.describe()) } } // Defining ball characteristics trait Ball { def describe(): String = "ball" } // Define color characteristics trait ColorBall extends Ball { var color: String = "red" override def describe(): String = color + "-" + super.describe() } // Define category characteristics trait CategoryBall extends Ball { var category: String = "foot" override def describe(): String = category + "-" + super.describe() } // Define a custom ball class class MyFootBall extends CategoryBall with ColorBall { // Specify the method of the parent class to be called through super[clazz] override def describe(): String = "my ball is a " + super[CategoryBall].describe() }
2.4 summary
- The method called by super in the trait depends on the linearization of the class and the traits mixed into the class
- When calling super in a class, the called method is the last one up to the top of the chain (the one on the right), and if the parent method still has super method, the chain call will be called.
- The super method is called in the reverse order of the constructor until the method in the lowest parent class is called
- You can specify the specific parent class to be called by super[clazz]