Scala 【 9 Trait 】

Trait

​ 可以将 Trait 作为接口来使用,此时的 Triat 就与 Java 中的接口非常类似。

​ 在 Triat 中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可。

​ 类可以使用 extends 关键字继承 trait,注意并不是 implement,而是 extends,在 Scala 中没有 implement 的概念,无论继承类还是 trait,统一都是 extends 。

​ 类继承 trait 后,必须实现其中的抽象方法,实现时不需要使用 override 关键字。

​ Scala 不支持对类进行多继承,但是支持多重继承 trait,使用 with 关键字即可。

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 来定义了通用的日志打印方法。

// 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,就直接被添加到了类中。

​ 所以看起来虽然一样,但是底层的原理是不一样的。

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,提供具体的值。

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 的方法,而类的其他对象则没有。

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 的方法开始执行,然后依次往左执行,形成一个调用链条。

​ 这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖。

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 修饰。

trait Logger{
    def log(msg:String)
}

trait MyLogger extends Logger{
    abstract override def log(msg:String) {
        super.log(msg)
    }
}
混合使用trait的具体方法和抽象方法

​ 在 trait 中,可以混合使用具体方法和抽象方法。

​ 可以让具体方法依赖于抽象方法,而抽象方法则放到继承 trait 的类中去实现。

​ 这种 trait 其实就是设计模式中的模板设计模式的体现。

// 在这里的体现就是 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

​ 测试结果如下:

//这是在命令提示符界面的输出结果。
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 的类的父类。

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)
    }
}

发布了357 篇原创文章 · 获赞 58 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章