10 特质

  • 类可以实现任意数量的特质
  • 特质可以要求实现他们的类具备特定的字段、方法或超类
  • 特质可以提供方法和字段的实现
  • 多个特质叠加时,后面的特质其方法先被执行

特质

  • 同时拥有抽象方法和具体方法,以及状态
  • 类可以实现多个特质
  • 特质中未被实现的方法默认就是抽象的
  • 继承特质使用extends而不是implements
  • 多个特质使用with
  • 所有的Java接口都可以作为Scala特质使用
  • extends后面的Logger with Cloneable with Serializable 可以看做一个整体,然后再由类扩展
trait Logger{
  //抽象方法,不提供具体实现,可以加abstract也可以不加
  def log(msg:String)
}

class ConsoleLogger extends Logger with Cloneable with Serializable {
  //实现抽象方法可以加override也可以不加
  override def log(msg: String): Unit = {
    println(msg)
  }
}

带有具体实现的特质

  • 特质中的方法不一定是抽象
trait ConsoleLogger{
  def log(msg:String): Unit ={
    println(msg)
  }
}

class SavingsAccount extends Account with ConsoleLogger{
  def withdraw(amount:Double): Unit ={
    if (amount > balance) log("Insufficient funds")
    else balance -= amount
  }
}

class Account {
  val id = Account.newUniqueNumber()
  protected var balance = 0.0

  def deposit(amount: Double): Unit = {
    balance += amount
  }
  def CXYE={
    println("余额:"+balance)
  }
}
object Account{
  private var lastNumber = 0
  private def newUniqueNumber():Int={
    lastNumber+=1
    lastNumber
  }
}

带有特质的对象

  • 构造对象时可以添加特质
  • 一个抽象类不能被实例化,不管是否包含抽象字段或方法。如果抽象类的所有字段方法都是具体的,并且扩展了一个特质,那么这个抽象类就能实例化为带特质的对象
abstract class A {val a="sr"}
trait B
new A with B
// res1: A with B = $anon$1@3f1158ee
trait Logger{
  def log(msg:String)
}
trait ConsoleLogger extends Logger{
  def log(msg:String): Unit ={
    println(msg)
  }
}
//现在SavingsAccount可以被实例化了
//val c = new SavingsAccount with ConsoleLogger
abstract class SavingsAccount extends Account with Logger{
    def withdraw(amount:Double): Unit ={
      if (amount > balance) log("Insufficient funds")
      else balance -= amount
    }
}

class Account {
  val id = Account.newUniqueNumber()
  protected var balance = 0.0

  def deposit(amount: Double): Unit = {
    balance += amount
  }
  def CXYE={
    println("余额:"+balance)
  }
}
object Account{
  private var lastNumber = 0
  private def newUniqueNumber():Int={
    lastNumber+=1
    lastNumber
  }
}

叠加在一起的特质

  • 最后出现的方法先被调用,其次往前推进,最前面的方法最后被调用
  • 对于acc1,ShortLogger的log方法先被执行,“Insufficient funds"被截断后变为了"Insufficient”,然后super.log调用TimestampLogger的log方法将"Insufficient"加上时间戳,最后是ConsoleLogger的log方法将加上时间戳后的字符串打印到屏幕
  • 对于acc2,TimestampLogger的log方法先被执行,然后super.log调用的是ShortLogger
trait Logger{
  //抽象方法,不提供具体实现,可以加abstract也可以不加
  def log(msg:String)
}
trait ConsoleLogger extends Logger{
  def log(msg:String): Unit ={
    println(msg)
  }
}
trait TimestampLogger extends ConsoleLogger{
  println("LongLogger")
  override def log(msg: String): Unit = super.log(s"${java.time.Instant.now()} ${msg}")
}
trait ShortLogger extends ConsoleLogger{
  println("ShortLogger")
  override def log(msg: String): Unit = super.log(if(msg.length<=15) msg else s"${msg.substring(0,12)}")
}
abstract class SavingsAccount extends Account with Logger{
    def withdraw(amount:Double): Unit ={
      if (amount > balance) log("Insufficient funds")
      else balance -= amount
    }
}

val acc1 = new SavingsAccount with TimestampLogger with ShortLogger
acc1.withdraw(1000)
//LongLogger
//ShortLogger
//2019-12-15T04:04:04.337Z Insufficient
val acc2 = new SavingsAccount with ShortLogger with TimestampLogger
acc2.withdraw(1000)
//ShortLogger
//LongLogger
//2019-12-15T0
  • 控制具体哪个特质的方法被调用,使用super[特质].log(…),给出的类型必须是直接超类,不能是更远的特质或类
  • 例如,在ShortLogger中调用Super[ConsoleLogger].log,会直接调用ConsoleLogger的log方法,而不是先TimestampLogger再ConsoleLogger
super[ConsoleLogger].log(if(msg.length<=15) msg else s"${msg.substring(0,12)}")

特质中重写抽象方法

  • abstract override def log(msg:String)

当做富接口使用的特质

  • 依赖抽象方法实现一些方法
trait Logger{
  //抽象方法,不提供具体实现,可以加abstract也可以不加
  def log(msg:String)
  def info(msg:String)={log(s"INFO: ${msg}")}
  def warn(msg:String)={log(s"WARN: ${msg}")}
  def severe(msg:String)={log(s"SEVERE: ${msg}")}
}

abstract class SavingsAccount extends Account with Logger{
    def withdraw(amount:Double): Unit ={
      if (amount > balance) severe("Insuffient funds")
      else balance -= amount
    }
}

特质中的具体字段

  • 特质中的字段可以是具体的也可以是抽象的,取决于你是否给出初始值
  • 使用该特质的类会获得一个字段与之对应,这些字段不能被继承,只是简单的被添加到了子类中
  • 在JVM中一个类只能扩展一个超类,因此当继承自特质的时候,特质的字段不能以相同的方式继承
  • maxLength书上说不能继承,这里也能继承,不知道怎么回事
class SavingsAccount extends Account with ConsoleLogger with ShortLogger {
  balance = 0.01
  override val maxLength=20
  println(maxLength)
    def withdraw(amount:Double): Unit ={
      if (amount > balance) log("Insuffient funds")
      else balance -= amount
    }
}
trait Logger{
  //抽象方法,不提供具体实现,可以加abstract也可以不加
  def log(msg:String)
  def info(msg:String)={log(s"INFO: ${msg}")}
  def warn(msg:String)={log(s"WARN: ${msg}")}
  def severe(msg:String)={log(s"SEVERE: ${msg}")}
}
trait ConsoleLogger extends Logger{
  println("ConsoleLogger")
  def log(msg:String): Unit ={
    println(msg)
  }
}
trait TimestampLogger extends ConsoleLogger{
  println("LongLogger")
  override def log(msg: String): Unit = super.log(s"${java.time.Instant.now()} ${msg}")
}
trait ShortLogger extends ConsoleLogger{
  val maxLength = 15
  println("ShortLogger")
  override def log(msg: String): Unit = super[ConsoleLogger].log(if(msg.length<=maxLength) msg else s"${msg.substring(0,maxLength)}")
}


class Account {
  val id = Account.newUniqueNumber()
  protected var balance = 0.0

  def deposit(amount: Double): Unit = {
    balance += amount
  }
  def CXYE={
    println("余额:"+balance)
  }
}
object Account{
  private var lastNumber = 0
  private def newUniqueNumber():Int={
    lastNumber+=1
    lastNumber
  }
}

特质中的抽象字段

  • 未被初始化的字段在具体子类中必须被重写,跟抽象类一样

特质的构造顺序

  • 特质也可以有构造器,由字段的初始化和其他特质体中的语句构成
  1. 首先调用超类的构造器
  2. 特质的构造器在超类的构造器之后、类构造器之前执行
  3. 特质由左到右被构造
  4. 在每个特质中,父特质先被构造
  5. 如果一个特质共有一个父特质,而那个特质已经被构造,那么这个父特质不会被再次构造
  6. 所有特质构造完毕,子类被构造
  • 总结起来就是,从左到右,从祖宗到子孙的顺序。SavingAccount是最子孙的,最后被构造。
  • 对于下面的一个类,其构造顺序如下
  1. Account:超类
  2. Logger: 第一个特质的父特质ConsoleLogger的父特质,爷爷特质
  3. ConsoleLogger: 第一个特质的父特质
  4. TimestampLogger:第一个特质
  5. ShortLogger:第二个特质,其父特质和爷爷特质都已被构造,不再被构造了
  6. SavingAccount:类
class SavingAccount extends  Account with TimestampLogger with ShortLogger

初始化特质中的字段

  • 特质不能有构造器参数,每个特质都有一个无参数的构造器
  • 缺少构造器参数是特质与类之间的唯一技术差别
  • 初始化特质的字段得使用提前定义或懒值,
trait FileLogger extends Logger{
  val filename:String
  val out = new java.io.PrintStream(filename)
  def log(msg:String)={
    out.println(msg)
    out.flush()
  }
}
//下面的方法行不通,因为FileLogger构造器先于子类构造器,子类没构造之前就已经抛出了空指针异常,out那一句
//java.lang.NullPointerException
val acct = new SavingsAccount with FileLogger{
  val filename = "mylog.log"
}

//正确的方法,使用提前定义,注意两个with,带有特质的对象
val acct = new {val filename = "myapp.log"} with SavingAccount with FileLogger {
  log("hahaha")
}
//在类中,extends后跟预定义块
class SavingAccount extends {val filename=“mylog.log”} with Account with FileLogger{
  //类的具体实现
}

扩展类的特质

  • 类也可以扩展特质
  • 特质的超类是任何混入该特质的类的超类
  • 类A扩展自B和特质C,如果C有超类D,那么B是D的子类才可以,如果B和D不相关,那么就不能扩展了。

自身类型

  • 自身类型:如果特质的定义开头是this:类型A=>,它只能被混入指定类型A的子类,只能类型A的子类能扩展这个特质。
  • 结构类型:给出类必须有的方法,而不是类的名称,类定义的开头是this:{def 方法F}=>,这个特质可以被混入任何拥有方法F的类。

特质的背后发生了什么

  • 只有抽象方法的特质会被简单地变成java接口
  • 有具体方法的时候就变成抽象类了

trait Logger2{
  val maxLength = 15 //具体字段
  val minLength :Int //抽象字段
  def log(msg:String) //抽象方法
  def log2(msg:String)=println(msg) //具体方法
}

class Account2 extends Logger2{
  val minLength=0
  def log(msg:String)="aa"
}
//查看字节码
public class Account implements Logger2 {
  private final int minLength;
  private final int maxLength;
  public int maxLength();
  public void Logger2$_setter_$maxLength_$eq(int);
  public void log2(java.lang.String);
  public int minLength();
  public void log(java.lang.String);
  public Account();
}

public abstract class Logger2$class {
  public static void log2(Logger2, java.lang.String);
  public static void $init$(Logger2);
}
发布了57 篇原创文章 · 获赞 73 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/weixin_40450867/article/details/103546243