1.类的继承
(1) 重写属性或者方法时,要使用关键字override
(2) final修饰的属性或者方法不能被重写
(3) 重写规则:
a. 父类中的val属性,子类只能使用用val属性来重写
class Parent { val str: String = "aaa" } class Child extends Parent { override val str: String = "bbb" }
b. 父类无参数的def方法,子类可以使用def方法重写,也可以使用val属性重写
class Parent { def getStr: String = "aaa" def getNum() = { 1 } } class Child extends Parent { override val getStr: String = "bbb" override val getNum: Int = 2 }
c. 父类有参数的def方法,子类只能使用def方法来重写
d. 父类的get set方法,子类能使用val属性来重写
class Parent { def str: String = "aaa" def str_=(s: String) = { //set方法_与=之间不能有空格 } } class Child extends Parent { override var str: String = "bbb" }
e. 父类的var属性,子类不能重写
(4) 主构造器的继承
如果父类带有主构造器,子类的主构造器必须覆盖父类构造器的所有参数
class Parent(val str: String) { } class Child(str: String, val num: Int) extends Parent(str) { }
2.抽象类的继承
(1) 抽象类使用abstract修饰,其中可以定义没有初始值的属性和没有方法体的方法
(2) 重写抽象类属性和方法可以不使用override(但最好还是加上)
(3) 重写规则
a.抽象父类中没有初始值的var属性,可以使用var属性重写
abstract class Parent(val str: String) { var flag: Boolean val ch: Char def func: String } class Child(str: String, val num: Int) extends Parent(str) { override var flag = true override val ch = 'a' override val func = "abc" }
(4) 子类必须实现父类中未实现的属性或者方法(如果子类也是个抽象类,那就不必了)
3.特质的继承
(1) 特质使用trait修饰,其中可以定义没有初始值的属性和没有方法体的方法
(2) 特质的继承方式
a. 类继承特质extends…with…with…(extends后可以接类、抽象类、特质,with后只能接特质)
与继承抽象类相同。特质中已赋值的属性/方法直接继承到子类中,未赋值的属性/方法要在子类中重写。
trait Attr1 { val str: String } trait Attr2 { def func } class Child extends Attr1 with Attr2 { override val str = "abc" override def func { } }
b. 对象继承特质new…with…with…(new后接类名,with后接特质)
特质中已赋值的属性/方法直接继承到对象中,未赋值的属性/方法要在对象中被覆盖(不是重写哦)。
trait Attr1 { val str: String = "abc" } trait Attr2 { def str: String def getStr { println("attr2=" + str) } } class Child { } object Main { def main(args: Array[String]): Unit = { val child = new Child with Attr1 with Attr2 child.getStr } }
当然,这两种继承方式是可以混用的。
4.有意思的问题
(1) 继承中的属性初始化顺序
class Parent { val size = 1 val arr = new Array[String](size) } class Child extends Parent { override val size = 2 } object Main { def main(args: Array[String]): Unit = { val child = new Child println(child.arr.length) } }
输出的结果即不是1也不是2,而是0。因为父类属性初始化是在子类的环境下,所以arr构造使用的size是子类的。而在父类初始化完成之前,子类的属性是没有初始化的,所以子类的size是0。
两种方式解决该问题:
使用lazy
class Parent { val size = 1 lazy val arr = new Array[String](size) } class Child extends Parent { override val size = 2 } object Main { def main(args: Array[String]): Unit = { val child = new Child println(child.arr.length) } }
lazy修饰的属性,只在调用时才会初始化,所以输出的结果是2。
提前初始化
class Parent { val size = 1 val arr = new Array[String](size) } class Child extends {override val size = 2} with Parent { } object Main { def main(args: Array[String]): Unit = { val child = new Child println(child.arr.length) } }
使用extends…with…语法来进行提前初始化。extends{}中的表达式会在父类初始化前就进行,所以输出的结果是2。
(2) 特质继承,多个特质中含有相同的属性/方法
a. 类继承特质
子类重写了该属性/方法:就不会报错。
子类没重写该属性/方法:特质中一个属性/方法是有值的,其他都是未赋值的,就不会报错,该属性/方法以那个有值的为准。其他情况都报错。
b. 对象执行特质
对象中有该属性/方法:特质中所有属性/方法都不能有值,否则会报错。
对象中无该属性/方法:特质中有且只能有一个属性/方法有值,否则会报错。
可以为多个有相同属性/方法的特质添加一个父特质,这样多个相同属性/方法就不会冲突了:
trait BaseAttr { val str: String } trait Attr1 extends BaseAttr { override val str: String = "abc" } trait Attr2 extends BaseAttr { override val str: String = "def" }
但是相同属性/方法会被覆盖,顺序是越靠后的特质会覆盖前面的特质:
class Child1 extends Attr1 with Attr2 { } class Child2 { } object Extend { def main(args: Array[String]): Unit = { val child1 = new Child1 println(child1.str) // def val child2 = new Child2 with Attr1 with Attr2 println(child2.str) // def } }
(3) 抽象重写
这种场景出现在特质中(貌似是因为特质的方法是动态绑定的,所以父特质未实现方法也可以在子特质中调用)
trait Parent { def func } trait Child extends Parent { abstract override def func { ... super.func ... } }
这种场景有点像AOP,在super.func的前后插入切面。因为super.func是未实现的,所以要用abstract override。
5.如何选择?是抽象类还是特质
看了上面的介绍,是不是觉得抽象类和特质特别的相似。那什么时候使用抽象类,什么时候使用特质呢?
1. 多重继承,当然要使用特质了。
2. 需要有构造方法的,要使用抽象类,因为特质没有构造方法。
3. 会被Java继承,要使用抽象类。
楼下是疯子。哈哈