Trait
可以将 Trait 作为接口来使用,此时的 Triat 就与 Java 中的接口非常类似。
在 Triat 中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可。
类可以使用 extends 关键字继承 trait,注意并不是 implement,而是 extends,在 Scala 中没有 implement 的概念,无论继承类还是 trait,统一都是 extends 。
类继承 trait 后,必须实现其中的抽象方法,实现时不需要使用 override 关键字。
Scala 不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可。
代码语言:javascript复制trait HelloTrait{
def sayHello(name:String)
}
trait MakeFriendsTrait{
def makeFriends(p:Person)
}
class Person(val name:String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable{
def sayHello(otherName:String) = println("Hello," otherName ",I'm " name)
def makeFriends(p:Person) = println("Hello,my name is " name ",your name is " p.name)
}
// 创建实例进行测试
val p = new Person("Li")
val q = new Person("Chy")
p.sayHello("Tom")
// 输出为: Hello,Tom,I'm Li
p.makeFriends(q)
// 输出为: Hello,my name is Li,your name is Chy
在Trait中定义具体方法
Scala 中的 Triat 可以不是只定义抽象方法,还可以定义具体方法,此时 trait 更像是包含了通用工具方法的东西,称为 triait 的功能混入的了类。
举例来看就是, trait 中可以包含一些很多类都通用的功能方法,比如打印日志等,spark 中就使用了 trait 来定义了通用的日志打印方法。
代码语言:javascript复制// example 1
// 从这里可以看到在 trait 里面是可以定义具体的方法的
trait Logger{
def log(msg:String) = println("log: " msg )
}
// 可以直接调用继承的 Logger 里面的方法
class Person(val name:String) extends Logger{
def sayHello{
println("Hello,I'm " name);
log("sayHello is invoked")
}
}
// 测试
val p = new Person("Li")
p.sayHello
//测试输出
// Hello,I'm LI
// log: sayHello is invoked
在Trait中定义具体字段
Scala 中的 Triat 可以定义具体 field,此时继承 trait 的类就自动获得了 trait 中定义的 field 。
但是这种获取 field 的方式与继承 class 是不同的:如果是继承 class 获取的 field,实际是定义在父类中的;而继承 trait 获取的 field,就直接被添加到了类中。
所以看起来虽然一样,但是底层的原理是不一样的。
代码语言:javascript复制trait Person {
val eyeNum: Int = 2
}
class Student(val name: String) extends Person {
def sayHello = println("Hi, I'm " name ", I have " eyeNum " eyes.")
}
在Trait中定义抽象字段
Scala 中的 Triat 可以定义抽象 field,而 trait 中的具体方法则可以基于抽象 field 来编写。
但是继承 trait 的类,则必须覆盖抽象 field,提供具体的值。
代码语言:javascript复制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)
println("I'm" name ",I want to make friends with you!")
}
}
// 测试
val p1 = new Person("Li")
val p2 = new Person("Chy")
p1.makeFriends(p2)
为实例混入trait
有时我们可以在创建类的对象时,指定该对象混入某个 trait,这样,就只有这个对象混入该 trait 的方法,而类的其他对象则没有。
代码语言:javascript复制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!")
}
}
val p1 = new Person("Li")
p1.sayHello
// p1.sayHello 的输出为:Hi,I'm Li
// 可见 log("sayHello is invoked!") 并没有输出,这里还是调用的 Logged 里面的方法,什么都没有做。
val p2 = new Person("Li") with MyLogger
// 通过 with 动态混入到具体的对象中,因为混入的 trait 中覆盖了 Logged 中的方法,所以执行 MyLogger 中的 log 方法。
p2.sayHello
// p2.sayHello 的输出为:
// Hi,I'm Li
// log:sayHello is invoked!
trait调用链
Scala 中支持让类继承多个 trait 后,依次调用多个 trait 中的同一个方法,只要让多个 trait 的同一个方法中,在最后都执行 super. 方法即可。
类中调用多个 trait 中都有的这个方法时,首先会从最右边的 trait 的方法开始执行,然后依次往左执行,形成一个调用链条。
这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖。
代码语言:javascript复制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)
}
}
// 测试
val p = new Person("Li")
p.sayHello
// 结果
// Hello, Li
// check data: Li
// check signature: Li
在trait中覆盖抽象方法
在 trait 中,是可以覆盖父 trait 的抽象方法的。 此时如果要通过编译,就得给子 trait 的方法加上 abstract override 修饰。
代码语言:javascript复制trait Logger{
def log(msg:String)
}
trait MyLogger extends Logger{
abstract override def log(msg:String) {
super.log(msg)
}
}
混合使用trait的具体方法和抽象方法
在 trait 中,可以混合使用具体方法和抽象方法。
可以让具体方法依赖于抽象方法,而抽象方法则放到继承 trait 的类中去实现。
这种 trait 其实就是设计模式中的模板设计模式的体现。
代码语言:javascript复制// 在这里的体现就是 getName 并没有具体实现,后续的方法可以直接使用。
trait Valid{
def getName:String
def valid:Boolean = {
getName == "Li"
}
}
class Person(val name:String) extends Valid{
println(valid)
def getName = name
}
// 测试
val p1 = new Person("Li")
val p2 = new Person("chy")
// p1 的返回会有 true
// p2 的返回会有 false
trait的构造机制
在 Scala 中,trait 也是有构造代码的,也就是 trait 中的,不包含在任何方法中的代码。 而继承了 trait 的类的构造机制如下:
- 父类的构造函数执行;
- trait 的构造代码执行,多个 trait 从左到右依次执行;
- 构造 trait 时会先构造父 trait,如果多个 trait 继承同一个父 trait,则父 trait 只会构造一次;
- 所有 trait 构造完毕之后,子类的构造函数执行。
class Person{
println("Person's constructor!")
}
trait Logger{
println("Logger's constructor!")
}
trait MyLogger extends Logger{
println("MyLogger's constructor!")
}
trait TimeLogger extends Logger{
println("TimeLogger's constructor!")
}
class Student extends Person with MyLogger with TimeLogger{
println("Student's constructor!")
}
// 测试
val s = new Student
测试结果如下:
代码语言:javascript复制//这是在命令提示符界面的输出结果。
scala> val p = new Student
Person's constructor!
Logger's constructor!
MyLogger's constructor!
TimeLogger's constructor!
Student's constructor!
p: Student = Student@61911947
从以上可以看出,当创建时,先是构造父类,再构造 trait ,从左到右执行 constructor,对于 MyLogger 和 TimeLogger 来说,先构造了 Logger ,虽然这两个都继承了 Logger ,但是输出只有一行,也就是只会构造一次,然后是 Student 自己的 constructor 。
trait field的初始化
在 Scala 中,trait 是没有接收参数的构造函数的,这是 trait 与 class 的唯一区别,但是如果需求就是要 trait 能够对 field 进行初始化, 使用 Scala 中非常特殊的一种高级特性——提前定义 。
trait继承class
在 Scala 中,trait 也可以继承自 class ,此时这个 class 就会成为所有继承该 trait 的类的父类。
代码语言:javascript复制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)
}
}