面向对象编程之Trait
trait基础知识
- 将trait作为接口使用
- 在trait中定义具体方法
- 在trait中定义具体字段
- 在trait中定义抽象字段
trait高级知识
- 为实例对象混入trait
- trait调用链
- 在trait中覆盖抽象方法
- 混合使用trait的具体方法和抽象方法
- trait的构造机制
- trati字段的初始化
- 让trait继承类
将trait作为接口使用
- trait作为接口,和java中的接口非常类似
- 在triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可
- 类可以使用extends关键字继承trait,注意,这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是trait,统一都是extends
- 类继承trait后,必须实现其中的抽象方法,实现时不需要使用override关键字
- scala不支持对类进行多继承,而是支持多重继承trait,使用with关键字即可
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait HelloTrait{
def sayHello(name:String)
}
trait MakeFriendsTrait{
def makeFriend(p:Person)
}
class Person(val name:String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable
{
def sayHello(name:String) = println("Hello," + name)
def makeFriend(p:Person) = println("Hello,my name is "+name +",your name is "+p.name)
}
// Exiting paste mode, now interpreting.
defined trait HelloTrait
defined trait MakeFriendsTrait
defined class Person
scala> val p = new Person("Tom")
p: Person = Person@41eb94bc
scala> p.makeFriend(p)
Hello,my name is Tom,your name is Tom
scala> p.sayHello("jike")
Hello,jike
在Trait中定义具体的方法
- scala中的Triat可以不是只定义抽象方法,还可以定义具体方法,此时trait更像是包含了通用工具方法的东西
- 就想trait的功能混入了类
- 举例来说,trait中可以包含一些很多类都通用的功能方法,比如打印日志等等,spark中就使用了trait来定义了通用的日志打印方法
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Logger{
def log(message :String) = println(message)
}
class Person(val name:String) extends Logger{
def makeFriends(p:Person){
println("Hi,I'm "+name+",I'm glad to make friends with you," + p.name)
log("makeFriends method is invoked with parameter Person[name="+p.name+"]")
}
}
// Exiting paste mode, now interpreting.
defined trait Logger
defined class Person
scala> val p = new Person("Tom")
p: Person = Person@6a6e9289
scala> p.makeFriends(p)
Hi,I'm Tom,I'm glad to make friends with you,Tom
makeFriends method is invoked with parameter Person[name=Tom]
在trait中定义具体字段
- scala中trait可以定义具体field,此时继承trait的类就可以自动获得trait中定义的field
- 但是这种获取field的方式与继承class是不同的:如果是继承class获得的field,实际是定义在父类中的;而继承trait获取的field,就直接被添加到类中
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Person{
val eyeNum:Int = 2
}
class Student(val name:String) extends Person{
def sayHello = println("Hi,I'm "+name+",I have "+eyeNum+" eyes")
}
// Exiting paste mode, now interpreting.
defined trait Person
defined class Student
scala> val s = new Student("Tom")
s: Student = Student@12765ebd
scala> s.sayHello
Hi,I'm Tom,I have 2 eyes
在Trait中定义抽象字段
- scala中的Trait可以定义抽象field,而trait中的具体方法则可以基于抽象field来编写
- 但是继承trait的类,则必须覆盖抽象field,提供具体的值
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait SayHello{
val msg:String
def sayHello(name:String) = println(msg+","+name)
}
class Person(val name:String) extends SayHello{
val msg:String = "hello"
def makeFriends(p:Person){
sayHello(p.name)
print("I'm "+name+",I want to make friends with you!")
}
}
// Exiting paste mode, now interpreting.
defined trait SayHello
defined class Person
scala> val p = new Person("Tom")
p: Person = Person@67cd193d
scala> p.msg
res4: String = hello
scala> p.makeFriends(p)
hello,Tom
I'm Tom,I want to make friends with you!
为实例混入trait
- 有时候我们可以在创建类的对象时,指定该对象混入某个trait,这样,就只有这个对象混入该trait的方法,而类的其他对象则没有
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Logged{
def log(msg:String){}
}
trait MyLogger extends Logged{
override def log(msg:String){println("log:"+msg)}
}
class Person(val name:String) extends Logged{
def sayHello{println("Hi,I'm "+name);log("sayHello is invoked!")}
}
// Exiting paste mode, now interpreting.
defined trait Logged
defined trait MyLogger
defined class Person
scala> val p1 = new Person("leo")
p1: Person = Person@20307cb9
scala> p1.sayHello
Hi,I'm leo
scala> val p2 = new Person("jack") with MyLogger
p2: Person with MyLogger = $anon$1@192800d
scala> p2.sayHello
Hi,I'm jack
log:sayHello is invoked!
trait调用链
- scala中支持多个trait,一次调用多个trait中的同一个方法,只要让多个trait的同一个方法中,在最后都执行super.方法即可
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Handler{
def handle(data:String){}
}
trait DataValidHandler extends Handler{
override def handle(data:String){
println("check data:" +data)
super.handle(data)
}
}
trait SignatureValidHandler extends Handler {
override def handle(data:String){
println("check signature: "+data)
super.handle(data)
}
}
class Person(val name:String) extends SignatureValidHandler with DataValidHandler{
def sayHello = { println("Hello, " + name);handle(name)}
}
// Exiting paste mode, now interpreting.
defined trait Handler
defined trait DataValidHandler
defined trait SignatureValidHandler
defined class Person
scala> val p1 = new Person("Tom")
p1: Person = Person@5fefb35c
scala> p1.sayHello
Hello, Tom
check data:Tom
check signature: Tom
在trait中覆盖抽象方法
- 在trait中,是可以覆盖父trait的抽象方法的
- 但是覆盖时,如果使用了super.方法的代码,则无法通过编译。因为super.方法就会去调用父trait的抽象方法,此时子trait的该方法还是会被认为是抽象的
- 此时如果要通过编译,就得给子trait的方法加上abstract override修饰
trait Logger{
def log(msg:String)
}
trait MyLogger extends Logger{
abstract override def log(msg:String) {super.log(msg)}
}
混合使用trait的具体方法和抽象方法
- 在trait中,可以混合使用具体方法和抽象方法
- 可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现
- 这种trait其实就是设计模式中的模板设计模式的体现
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait Valid{
def getName:String
def valid:Boolean = {
getName == "leo"
}
}
class Person(val name:String) extends Valid{
println(valid)
def getName = name
}
// Exiting paste mode, now interpreting.
defined trait Valid
defined class Person
scala> val p = new Person("leo")
true
p: Person = Person@30749de8
scala> p.getName
res10: String = leo
scala> val p2 = new Person("Tom")
false
p2: Person = Person@622a4589
scala> p2.getName
res11: String = Tom
trait的构造机制
- 在scala中,trait也是有构造代码的,也就是trait中,不包含在任何方法中的代码
- 而继承了trait的类的构造机制如下
- 父类的构造函数执行
- trait的构造代码执行,多个trait从左到右依次执行
- 构造trait时会先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次
- 所有trait构造完毕之后,子类的构造函数执行
scala> :paste
// Entering paste mode (ctrl-D to finish)
class Person{println("Person constructor")}
trait Logger{println("Logger constructor")}
trait MyLogger extends Logger{println("MyLogger constructor")}
trait TimeLogger extends Logger{println("TimeLogger constructor")}
class Student extends Person with MyLogger with TimeLogger{
println("Student constructor")
}
// Exiting paste mode, now interpreting.
defined class Person
defined trait Logger
defined trait MyLogger
defined trait TimeLogger
defined class Student
scala> val s = new Student
Person constructor
Logger constructor
MyLogger constructor
TimeLogger constructor
Student constructor
s: Student = Student@22caeb7f
trait field的初始化
- 在scala中,trait是没有接受参数的构造函数的,这是trait与clas的唯一区别,但是如果需求就是要trait能够对field进行初始化,我们可以使用scala中非常特殊的一种高级特性--提前定义
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait SayHello{
val msg:String
println(msg:String)
}
class Person
val p = new {
val msg:String = "init"
} with Person with SayHello
// Exiting paste mode, now interpreting.
init
defined trait SayHello
defined class Person
p: Person with SayHello = $anon$1@6ce7fb0c
scala> :paste
// Entering paste mode (ctrl-D to finish)
class Person extends{
val msg:String = "init"
} with SayHello{}
// Exiting paste mode, now interpreting.
defined class Person
scala> val p2 = new Person
init
p2: Person = Person@16c87aa2
scala> :paste
// Entering paste mode (ctrl-D to finish)
trait SayHello{
lazy val msg:String = null
println(msg.toString)
}
class Person extends SayHello{
override lazy val msg:String = "init"
}
// Exiting paste mode, now interpreting.
defined trait SayHello
defined class Person
scala> val p3 = new Person
init
p3: Person = Person@3219b550
trait继承class
- 在scala中,trait也可以继承自class,此时这个class就会为所有继承该trait的类的父类
scala> :paste
// Entering paste mode (ctrl-D to finish)
class MyUtil{
def printMessage(msg:String) = println(msg)
}
trait Logger extends MyUtil{
def log(msg:String) = printMessage("log: "+msg)
}
class Person(val name:String) extends Logger{
def sayHello{
log("Hi,I'm "+name)
printMessage("Hi,I'm "+name)
}
}
// Exiting paste mode, now interpreting.
defined class MyUtil
defined trait Logger
defined class Person
scala> val p = new Person("Jike")
p: Person = Person@3685cd73
scala> p.sayHello
log: Hi,I'm Jike
Hi,I'm Jike