scala学习之特质(trait)

特质,很像java中的接口,但是又有些不同,比如实现方法,当然java8也可以在接口中实现一个方法了,但是只能定义一个default方法。
当做接口使用

//特质
trait Logger {
   def log(msg:String)
}

trait ConsoleLogger extends Logger{  //extends not implements
   def log(msg: String){println(msg)} //不需要写 Overrride
  //在重写 特质的抽象方法是不需要写 Overrride关键字
  //如果需要的特质不止一个,可以使用with连接

  //特质中的方法并不一定是抽象的
  def write(msg:String){println(msg)}
}

含有具体实现
例子来源于 快学scala
先定义这么一个trait

trait Logged {
  def log(msg : String){}
}

写一个class继承它

class SavingAccount extends Account with Logged{
  def withdraw(ammount :Double): Unit ={
    if (ammount > balance) log("this is a test")
    else  balance -= ammount
  }

}

从代码上看,在Logged 中,我们并没有实现此方法,所以这里的log是无意义的。
但是在scala中并非如此。

trait ConsoleLogged extends Logged{
   override def log(msg:String){println(msg)}
}

定义一trait继承Logged,并实现log方法,然后锣鼓一响,好戏开场

object TestTrait extends App{
  val acct = new SavingAccount with ConsoleLogged
  acct.withdraw(2.2)
}

你会控制台有日志打印。
下面看下多个trait的执行顺序,是一件非常有意思的事情,首先写几个trait,代码如下:

trait ConsoleLogged extends Logged{
   override def log(msg:String){println("console=>"+msg )}
}
trait TimeStampLogger extends Logged{
  override def log(msg:String){println(msg+".print at."+new Date())}
}
trait ShortLogger extends Logged{
  val maxLength = 15
  override def log(msg:String): Unit ={
    super.log(
      if (msg.length<=maxLength) msg else msg.substring(0,maxLength)+"...short"
    )
  }
}

测试:

  val acct = new SavingAccount with ConsoleLogged
  acct.withdraw(3.2)

  val acct1 = new SavingAccount with ConsoleLogged with TimeStampLogger with ShortLogger
  acct1.withdraw(2.2)

  val acct2=new SavingAccount with TimeStampLogger with ConsoleLogged with  ShortLogger
  acct2.withdraw(2.5)

  val acct3=new SavingAccount with ConsoleLogged with ShortLogger with  TimeStampLogger
  acct3.withdraw(2.5)

  val acct4=new SavingAccount with TimeStampLogger with ShortLogger with  ConsoleLogged
  acct4.withdraw(2.5)

输出如下:

console=>SavingAccount print =>
SavingAccount p...short.print at.Thu Aug 13 17:54:17 CST 2015
console=>SavingAccount p...short
SavingAccount print =>.print at.Thu Aug 13 17:54:17 CST 2015
console=>SavingAccount print =>

从打印结果分析看,如果trait是从左到右开始执行,那么第三条和第五条没有输出是无法解释的;那么如果从右开始执行呢,可以看到第二条输出,先执行ShortLogger,调用super.log,执行TimeStampLogger,那么ConsoleLogged似乎没有被调用;以此解释第三条输出也是说的通的,而TimeStampLogger没有被执行,那么第四条数据呢,只是执行了TimeStampLogger,同理第五条也是如此,如果没有super,似乎前面的trait中的方法不会被调用,但是,如果我在TimeStampLogger如此做的话,需要打上abstract标签,输出结果和上面相同,这里有些疑问,暂且存疑。
在特质中声明字段
在trait中字段声明可以是具体的,也可以是抽象的,如果在字段声明时初始化,就是具体的。如果有子类继承吃trait,改字段在JVM中,实际上是属于子类的字段,和子类字段放在一起。
声明抽象字段

trait ShortLogger extends Logged{
  val maxLength : Int
  override def log(msg:String): Unit ={
    super.log(
      if (msg.length<=maxLength) msg else msg.substring(0,maxLength)+"...short"
    )
  }
}

需要在子类中对其初始化,例如:

class SavingAccount extends Account  with ShortLogger{
  def withdraw(ammount :Double): Unit ={
    if (ammount > balance) log("SavingAccount print =>")
    else  balance -= ammount
  }

  override val maxLength: Int = 15
}

也可以如此:

  val acct1 = new SavingAccount with ConsoleLogged with TimeStampLogger with ShortLogger{
    val maxLength = 15
  }

特质的构造顺序
trait是有构造顺序的,初始化一个类,构造顺序如下:

  1. 先调用超类的构造方法
  2. trait构造方法在超类构造方法之后和类的构造方法之前执行
  3. trait由左到右被构造(待验证)
  4. 在trait中,父trait首先被构造
  5. 如果多个trait共有一个父trait,而父trait已经被构造,则不会父trait不会再被构造
  6. 所有的trait被构造完毕,类被构造
new SavingAccount with ConsoleLogged  with TimeStampLogger with ShortLogger

根据《快学scala》中介绍的构造顺数,线性化相反的方向,串接并去掉重复选项,右侧胜出
以上面的为例子:
SavingAccount >>ShortLogger>>TimeStampLogger >>ConsoleLogged >>Logged>>Account
但是输出结果:

SavingAccount p...short.print at.Fri Aug 14 17:17:41 CST 2015

但是从输出结果上分析,少了ConsoleLogged 的输出结果,至于为什么,恕在下才疏学浅,还没有找到结果。

scala执行时需要JVM的,但是JVM只支持单一继承。那么scala的trait在JVM中是怎么被翻译的呢?

如果scala只有抽象方法时,trait被翻译成接口,如果trait有具体的方法实现,scala会创建一个伴生类,该伴生类用静态方法存放特质的方法,这些伴生类中不会有任何字段。特质中的字段会对应到接口中抽象的setter和getter方法。如果trait继承自某个超类,伴生类不会继承改超类,该超类会被任何实现该特质的类继承。

用放荡不羁的心态过随遇而安的生活
原文地址:https://www.cnblogs.com/re-myself/p/5532484.html