Scala中的继承顺序

1.特质构造顺序

给出以下类和特质作为案例

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....")
}

image-20210708215054038

1.1 声明类的同时混入特质

此时构造顺序应该为

① 调用当前类的超类构造器
② 第一个特质的父特质构造器
③ 第一个特质构造器
④ 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
⑤ 第二个特质构造器
⑥ 重复4,5的步骤(如果有第3个,第4个特质)
⑦ 当前类构造器

object TraitCreate {
  def main(args: Array[String]): Unit = {
    // 按照深度优先对类进行构造
    // E -> A -> B -> C -> D -> F
    new FF
  }
}
1.2 在构建对象时,动态混入特质

此时构造顺序应该为

① 调用当前类的超类构造器
② 当前类构造器
③ 第一个特质构造器的父特质构造器
④ 第一个特质构造器.
⑤ 第二个特质构造器的父特质构造器, 如果已经执行过,就不再执行
⑥ 第二个特质构造器
⑦ 重复5,6的步骤(如果有第3个,第4个特质)

object TraitCreate {
  def main(args: Array[String]): Unit = {
    // 按照深度优先原则对类进行构造
    // E -> K -> A -> B -> C -> D
    new KK with CC with DD
  }
}
1.3 总结

两种混入特质的方式构造顺序的差异主要体现在当前类构造器创建的时机

若使用声明类的同时混入特质, 则在混入特质时,该对象还没有创建,因此在特质构造器创建完成后才会创建当前类构造器。
若在构建对象时动态混入特质,则可以理解成在混入特质时,对象已经创建了,因此调用超类构造器而未混入特质时就已经创建当前类构造器了。

2.调用super方法的顺序

2.1 Scala中的线性化

特质中的super调用的方法取决于类和混入该类的特质的***线性化***(linearization)。

当你用new实例化一个类的时候,Scala会将类及它所有继承的类和特质都拿出来,将它们线性地排列在一起。然后,当在某一个类中调用super时,被调用的方法是这个链条中向上最近的那一个(最右侧一个)。如果除了最后一个方法外,所有的方法都调用了super,那么最终的结果就是叠加在一起的行为。

线性化的确切顺序在Scala Language Specification里有描述。这个描述有点复杂,下面网站中有详细解释,有兴趣的同学可以下去研究一下:

Scala Language Specification - Classes and Objects

总体来讲,就是说在任何线性化中,类总是位于所有它的超类和混入的特质之前

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.

因此,当你写下调用super的方法时,那个方法绝对是在修改超类和混入特质的行为,而不是反过来。这里有点绕,自己的理解是,按照构造器的反方向来调用super方法,直到super调用到最深一层的对象的同名方法为止

Martin Odersky编著的Programming in Scala一书中的案例做说明

object Linearization {
  def main(args: Array[String]): Unit = {
    /**
     * 输出结果
     *    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}")
}

Cat类的继承关系和线性化如下图所示

scala

Cat的线性化从后到前的计算过程如下。Cat线性化的最后一个部分是其超类Animal的线性化。这段线性化被直接复制过来不加修改。由于Animal并不显式地扩展某个超类也没有混入任何超特质,它默认扩展自AnyRef,而AnyRef扩展自Any。这样Animal的线性化看上去就是这个样子的:

Animal --> AnyRef --> Any

线性化的倒数第二个部分是首个混入(即Furry特质)的线性化,不过所有已经出现在Animal线性化中的类都不再重复出现,每个类在Cat的线性化当中只出现一次。结果是:

Furry --> Animal --> AnyRef --> Any

在这个结果之前,是FourLegged的线性化,同样地,任何已经在超类或首个混入中拷贝过的类都不再重复出现:

FourLeggerd --> HasLegs -- >Furry --> Animal --> AnyRef --> Any

最后,Cat线性化中的第一个类时Cat自己:

Cat --> FourLeggerd --> HasLegs -- >Furry --> Animal --> AnyRef --> Any

Cat类的线性化过程如下表所示

image-20210708225921335

2.2 案例说明

按照上面的思路整理了如下案例

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}")
  }
}

继承关系如下图所示,数字表示继承顺序,数字越大越靠右,红色表示最靠右侧的继承关系。

image-20210708230801412

创建如下类并进行测试

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)
}

结果如下

# 深度优先原则进行构造
foo08
foo06
foo07
foo04
foo09
foo05
foo01
foo02
foo03
# 线性化方法调用顺序
I'm in foo03
I'm in foo02
I'm in foo01
I'm in foo05
I'm in foo09
# 输出的最终结果
foo09:foo05:foo01:foo02:foo03:fooApp

可以看到使用super进行调用时,会由最右侧的特质的方法进行调用,按照构建的相反方向对重构方法进行调用,直到调用到最底层的方法为止。

现在调整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)
}

结果如下,符合预期

# 深度优先原则进行构造
foo08
foo09
foo05
foo06
foo07
foo04
foo02
foo03
foo01
# 线性化方法调用顺序
I'm in foo01
I'm in foo03
I'm in foo02
I'm in foo04
I'm in foo07
# 输出的最终结果
foo07:foo04:foo02:foo03:foo01:fooApp

当然,如果在继承过程有某个特质未重写该方法或者未调用super方法,super调用类到该特质即会结束

object DiamondInherited {

  def main(args: Array[String]): Unit = {
    val fooApp = new FooApp
    fooApp.foo("fooApp")
  }

}

// 注释掉foo02中的super调用, 其他特质维持不变
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)
}

则会导致输出结果如下

# 深度优先原则进行构造
foo08
foo09
foo05
foo06
foo07
foo04
foo02
foo03
foo01
# 线性化方法调用顺序
I'm in foo01
I'm in foo03
I'm in foo02
# 由于foo2中没有调用super方法, 调用链至此已经结束, 无法继续输出结果
2.3 指定需要调用的特质

上面的例子都是通过集成关系确定需要调用的特质的方法,如果需要指定具体使用哪个特质的方法,可以通过super[clazz]的方式直接指定

object TraitOverlying {
  def main(args: Array[String]): Unit = {
    val myFootBall = new MyFootBall
    // my ball is a foot-ball
    println(myFootBall.describe())
  }
}

// 定义球类特征
trait Ball {
  def describe(): String = "ball"
}

// 定义颜色特征
trait ColorBall extends Ball {
  var color: String = "red"
  override def describe(): String = color + "-" + super.describe()
}

// 定义种类特征
trait CategoryBall extends Ball {
  var category: String = "foot"
  override def describe(): String = category + "-" + super.describe()
}

// 定义一个自定义球的类
class MyFootBall extends CategoryBall with ColorBall {
  // 通过 super[clazz] 指定需要调用的父类的方法
  override def describe(): String = "my ball is a " + super[CategoryBall].describe()
}
2.4 小结
  • 特质中的super调用的方法取决于类和混入该类的特质的线性化(linearization)
  • 在某一个类中调用super时,被调用的方法是这个链条中向上最近的那一个(最右侧一个),如果父特质中仍有super方法则会链式调用
  • 调用super方法的顺序与构造器的相反,直到调用最底层的父类特质中的方法
  • 可以通过super[clazz]方式指定需要调用的具体的父类特质
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值