1.特质
在Scala中,特质Trait与Java中的接口不同。特质可以同时具有抽象方法以及具体的方法,并且Scala类可以实现多个特质。
2.特质中的抽象方法和具体方法
在Scala中,特质既可以拥有抽象方法,也可以拥有实现方法。只有抽象方法或者抽象字段的特质,可以当作接口来使用。
在scala中,子类实现特质时,使用extends关键字。并且在重写抽象方法或者抽象字段时,可以省略掉overwrite关键字
//构造一个Person特质,定义sayHello的抽象方法,和一个name的抽象字段trait Person{ def sayHello(msg:String) val name:String}//构造一个Student类实现Person特质,重写sayHello方法和通过主构造函数实现name字段class Student(val name:String) extends Person{ def sayHello(msg:String){println(msg+", my name is "+name)}}
特质中的实现方法,在子类中直接存在,可以直接调用(类型于继承,子类可以继承父类的方法,区别在于继承特质在编译时会将方法定义在子类中)
//定义一个具有sayHello具体方法的特质Persontrait Person{ def sayHello(name:String){ println("Hello, My Name Is "+name) }}//定义一个Student类class Student{}object xxx { def main(args: Array[String]): Unit = { //创建一个Student实例,并混入特质Person val s = new Student with Person s.sayHello("Jack") }}
在特质的具体方法中也可以调用抽象方法,但是在子类中必须重写抽象方法。
//定义一个具有sayHello具体方法的特质Person,以及一个抽象方法getMsgtrait Person{ def getMsg:String def sayHello(name:String){ println(getMsg + name) }}//定义一个Student类class Student{}object xxx { def main(args: Array[String]): Unit = { //创建一个Student实例,并混入特质Person,重写getMsg抽象方法 val s = new Student with Person{ override def getMsg = "Hi, My Name Is " } s.sayHello("Jack") }}
3.特质中的抽象字段和具体字段
特质中具体字段可以在子类中直接使用,而抽象字段必须在子类中重写。
//定义一个特质类Person,一个具体字段eyes,一个抽象字段nametrait Person{ val eyes = 2 val name:String}//定义一个Student类class Student{}object xxx { def main(args: Array[String]): Unit = { //创建一个Student实例,并混入特质Person,重写name字段 val s = new Student with Person{ val name = "Jack" } println(s.eyes) println(s.name) }}控制台输出:2Jack
另外,在具体方法中可以使用抽象字段,但是在子类中必须重写抽象字段。
//定义一个特质类Person,一个具体方法sayHeool,一个抽象字段nametrait Person{ val name:String def sayHello(){println("Hello, My Name Is "+name)}}//定义一个Student类class Student{}object xxx { def main(args: Array[String]): Unit = { //创建一个Student实例,并混入特质Person,重写name字段 val s = new Student with Person{ val name = "Tom" } s.sayHello() }}控制台输出:Hello, My Name Is Tom
4.特质调用链
如下代码,在main方法中,创建一个TraitTest对象t1,并为对象混入TraitA、TraitB两个特质。但是在TraitA、TraitB两个特质中,有一个相同的方法log(msg:String)。如果代码中没有使用到super.方法名()来实现调用链,而直接调用t1.log(...)方法是会报错的。
在Scala特质调用链中的super并不是父类的意思,而是在混入特质时,如:new TraitTest with TraitA whih TraitB 代码中 with ... with ... 的顺序。
trait TraitSuper{ def log(msg:String){ println("TraitSuper被调用") println(msg) }}trait TraitA extends TraitSuper{ override def log(msg:String){ println("TraitA被调用") super.log("TraitA: "+msg) }}trait TraitB extends TraitSuper{ override def log(msg:String){ println("TraitB被调用") super.log("TraitB: "+msg) }}class TraitTest{}object xxx { def main(args: Array[String]): Unit = { val t1 = new TraitTest with TraitA with TraitB t1.log("msg...") }}
控制台输出如下:
TraitB被调用TraitA被调用TraitSuper被调用TraitA: TraitB: msg...
在Trait调用链中,调用的顺序即是with ... with ... 从右往左的顺序
5.特质的构造顺序
特质的构造器,由字段的初始化以及其他特质体内的语句组成。如:
trait Person{ val name:String //这里属于构造器 val eyes = 2 //这里属于构造器 println("构造器代码执行") //这里属于构造器 def sayHello(){println("Hello, My Name Is "+name)}}
比较复杂的情况,如下:
trait Person{ println("Person初始化")}trait Boy extends Person{ println("Boy初始化")}trait Man extends Person{ println("Man初始化")}class 小明他爸{ println("小明他爸初始化")}class Student extends 小明他爸 with Boy with Man { println("Student初始化")}
当创建Student实例时,控制台输出:
小明他爸初始化Person初始化Boy初始化Man初始化Student初始化
构造顺序:
1.小明他爸 (超类)2.Person (第一个特质的父特质)3.Boy (第一个特质)4.Man (第二个特质)5.Student (类)
总结特质构造器的执行顺序:
1.首先调用超类的构造器
2.从右往左调用特质的构造器,如果该特质有父特质,则会先调用父特质的构造器
3.子类构造
注意:如果多个特质持有共同一个父特质,则该父特质只会初始化一次
6.特质继承类
7.实现特质的两种方式
在Scala中,混入特质的方式有两种。
1.在定义类时混入特质
trait Person{}class Student extends Person{} //混入Person特质
2.在创建对象时混入特质
trait Person{}object xxx{ def main(args: Array[String]): Unit = { val s = new Student with Person //创建对象时混入Person特质 }}
两种方式不同处在于:
定义类的时候混入特质时,该类所有对象都具有该特质的方法和属性。
创建对象的时候混入特质,则只有该对象具有特质的方法和属性。