Scala笔记3-特质trait详解

这里,紧接着上一篇博文scala学习笔记2的特质Trait来写,博主在学习scala和Spark的时候,觉得Trait这一特性非常重要,所以便有了此篇。

1.trait基础知识

1.1 将trait作为接口使用

首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口(interface)非常类似。在triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可
类可以使用extends关键字继承trait,注意,这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是trait,统一都是extends
类继承trait后,必须实现其中的抽象方法,实现时不需要使用override关键字。scala不支持对类进行多继承,但是支持多重继承trait,使用with关键字即可

1.2 在trait中定义具体方法,具体字段

Scala中的Trait可以不是只定义抽象方法,还可以定义具体方法,具体字段。

trait CarId{
//  var id: Int   //这里id没有初始化 所以在子类继承该trait时,必须覆盖抽象field,提供具体的值
  var id=10
  def currentId(): Int     //定义了一个抽象方法
  def test(msg:String): Unit ={
    println(msg)
  }
}
class BYDCarId extends CarId{ //使用extends关键字
//  override var id = 10000 //BYD汽车编号从10000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
class BMWCarId extends CarId{ //使用extends关键字
//  override var id = 20000 //BMW汽车编号从10000开始
def currentId(): Int = {id += 12; id} //返回汽车编号
}
object TraitLearn {
  def main(args: Array[String]): Unit = {
    val myCarId1 = new BYDCarId()
    val myCarId2 = new BMWCarId()
    printf("My first CarId is %d.\n",myCarId1.currentId)
    myCarId1.test("123")
    printf("My second CarId is %d.\n",myCarId2.currentId)
    myCarId2.test("abcd")
  }
}

trait中定义具体字段:

  • Scala中的Triat可以定义具体field,此时继承trait的类就自动获得了trait中定义的field
  • 但是这种获取field的方式与继承class是不同的:如果是继承class获取的field,实际是定义在父类中的; 而继承trait获取的field,就直接被添加到了类中

1.3 在trait中定义抽象字段

简单理解,就是在trait里面声明的成员属性未赋初值,所以在class继承trait 如果要调用trait中的成员属性 必须复写给器赋初始值。

2.trait高级知识

2.1 为实例对象混入trait

有时我们可以在创建类的对象时,指定该对象混入某个trait, 这样,就只有这个对象混入该trait的方法,而类的其他对象则没有。比如下面这个例子:

object TraitLearn03 {

  trait Log {
    def log(msg: String){}
      //编程时注意 这必须加上大括号 表示该方式实现了 非抽象方法
  }

  trait MyLog extends Log {
    override def log(msg: String) = println("log: " + msg)
  }

  class Per(val name: String) extends Log {
    def sayHello: Unit = {
      print("Hi, I'm " + name+"; ")
      log("12345")
    }
  }

  def main(args: Array[String]): Unit = {
    val p1 = new Per("xjh")
    p1.sayHello
    println()
    val p2 = new Per("kobe") with MyLog //这里with MyLog指的是对象p2混入trait MYLog
    p2.sayHello
  }
}

因为对象p2在初始化的时候混入了trait MyLog,所以p2。sayHello的输出与p1会不同。

Hi, I'm xjh; 
Hi, I'm kobe; log: 12345

2.2 trait调用链

Scala中支持让类继承多个trait后,依次调用多个trait中的同一个方法,只要让多个trait的同一个方法中,在最后都执行super.方法即可
类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条
这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现依赖

object TraitLearn04 {
trait Handle{
  def handle(data:String){}
}
  trait DataHandle extends Handle{
    override def handle(data: String): Unit = {
      println("check data: "+data)
      super.handle(data)
    }
  }
  trait SignHandle extends Handle{
    override def handle(data: String): Unit = {
      println("check sign: "+data)
      super.handle(data)
    }
  }
  class Pree(val name:String) extends SignHandle with DataHandle{
        //trait从右往左 依次执行
    def sayHello: Unit ={
      println("Hello ,"+name)
      handle(name)
    }
  }

  def main(args: Array[String]): Unit = {
    val p=new Pree("xjh")
    p.sayHello
  }
}
Hello ,xjh
check data: xjh
check sign: xjh

注意:在这个例子中,特质Handle的两个字特质SignHandle,DataHandle在重写父特质handle方法时 都在方法里面最后加上的super.方法。我们都知道super.方法名 是显示的调用父类中的方法。在这里 因为class继承多个trait是从右往左,所以实质上这里SignHandle对应的handle方法里面不写super.方法名 也能输出同样的结果。但是为了保持代码的完整性和可移植性,建议都写上。

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

2.4 混合使用trait的具体方法和抽象方法

在trait中,可以混合使用具体方法和抽象方法
可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现
这种trait其实就是设计模式中的模板设计模式的体现

object TraitLearn06 {

  trait Valid {
    def getName: String

    def valid: Boolean = getName == "xjh"
  }

  class Ptest(val name: String) extends Valid {
    println(valid)
    override def getName: String = name
  }

  def main(args: Array[String]): Unit = {
    val p = new Ptest("xjh")
  }
}

2.5 trait的构造机制

在Scala中,trait也是有构造代码的,也就是trait中的,不包含在任何方法中的代码
而继承了trait的类的构造机制如下:

  • 1、父类的构造函数执行;
  • 2、trait的构造代码执行,多个trait从左到右依次执行;
  • 3、构造trait时会先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次;
  • 4、所有trait构造完毕之后,子类的构造函数执行
    这一部分面向对象语言都是大同小异,这里便不再编写demo记性测试

2.6 让trait继承类

在Scala中,trait也可以继承自class,此时这个class就会成为所有继承该trait的类的父类

object TraitLearn05 {
class MyUtil{
  def printMessage(msg:String)=println(msg)
}
  trait LoUtil extends MyUtil{
    def log(msg:String)=printMessage("log: "+msg)
  }
  class Prere(val name:String) extends LoUtil{
    def sayHello: Unit ={
      log("Hi, I'm "+name)
      printMessage("Hi,I'm "+name)
    }
  }

  def main(args: Array[String]): Unit = {
    val p=new Prere("jiahao")
    p.sayHello
  }
}

猜你喜欢

转载自blog.csdn.net/weixin_38073885/article/details/83993808