参考链接: Java中将final与继承一起使用
在之前的笔记Java静态属性和方法的继承问题中,通过具体的实验证明,在子类中重写父类的字段时并没有覆盖父类的字段,只是隐藏了父类的字段。而在scala中则不同,scala子类的同名字段会重写且覆盖父类的同名字段,这里做了个简单实验,并记录下来。
Parent.scala
class Parent {
val normalStr: String = "Normal member of parent."
def normalMethod() = {
println("Normal method of parent.")
}
}
定义了一个字段normalStr和一个方法normalMethod(),在Scala中,类的字段是由一个私有属性和对应的getter/setter方法组成的。
Child.scala
class Child extends Parent {
override val normalStr: String = "Normal member of child."
override def normalMethod() = {
println("Normal method of child.")
}
}
子类Child继承了父类Parent,并override父类的normalStr和normalMethod()。
TestMain和Result
object TestMain{
def main(args: Array[String]) {
val child: Child = new Child
println(child.normalStr)
child.normalMethod()
//val child1:Parent = child.asInstanceOf[Parent]
//采用Parent类型的变量指向创建的Child对象
val child1:Parent = new Child
println(child1.normalStr)
child1.normalMethod()
}
}
输出的结果如下:
Normal member of child.
Normal method of child.
Normal member of child.
Normal method of child.
从结果可以看出,子类重写并覆盖了父类的同名属性和方法
Scala子类的构造顺序
这里顺便记录下Scala子类的构造顺序,这里直接用书上给出的例子,以便后续查看:
先写两个类,一个父类Creature.scala,一个子类Ant.scala:
Creature
class Creature {
val range: Int = 10
val env: Array[Int] = new Array[Int](range)
def show(): Unit = {
println(range)
}
}
Ant
class Ant extends Creature {
override val range = 2
}
现在创建一个Ant的对象ant,那么ant.env.length的值是多少,凭第一感觉应该是10或者2,然而答案是0,接下来我写下ant创建的过程中构造器的运行顺序:
首先调用父类Creature的构造器(父类的构造器先于子类的构造器被调用),所以首先把range设置为10。为了后续的说明这里说明下,类的字段是由一个私有属性和对应的getter和setter方法组成的,而子类在重写父类的同名字段时,对于val类型的属性子类重写了getter方法。接下来初始化env数组,所以需要调用range的getter方法,然而子类已经重写了getter方法,且子类并没初始化,所有的字段都是对象创建过程中,内存清零后的默认值,所以此时range的值为0。这也就解释了上述问题的疑问。接下来调用子类的构造器,range被设为2。
所以在构造器中,对象的初始化不应该依赖于val的值,因为val的值对应的getter方法可能会被子类重写覆盖。解决办法有:
将val声明为final。(简单高效,但是不够灵活)在超类中将val声明为lazy。(简单灵活,但是不够高效)还有种就是子类中使用提前定义语法。(这个就不介绍了)
ant对象调用show()方法输出的则是子类range的值,即为2。而在Java中,则是父类的range的值:10。主要原因还是由于在Scala中,子类重写父类的属性或者方法,覆盖了父类的属性和方法,而在Java中,只有非静态的方法会被子类重写覆盖,而非静态/静态属性和静态方法都只是被隐藏了。
主要参考:《快学Scala》