Scala学习笔记 A2/L1篇 - 特质Traits

教材:快学Scala

chapter 10. 特质 Traits

  • Scala特质与Java接口不同,Scala特质可以给出这些特性的缺省实现
  • class可以implement任意数量的trait
  • trait可以要求实现它们的class具备特定字段/方法/超类
  • 叠加多个trait时,排在后面的trait的方法会更优先执行

10.1 为什么没有多重继承

  • 即不允许从多个超类继承,和Java一样
  • 原因 菱形继承问题 C++的解决方案:虚基类
  • Java 没有可以实现任意数量接口 但只能包含抽象方法 不能包含字段
  • Scala trait 同时拥有抽象方法和具体方法 未被实现的方法都默认是抽象的

10.2 当接口使用的特质

class ConsoleLogger extends Logger with Cloneable with Serializable { def log(msg: String) }

  • 1个超类 >=0个特质

10.3 带具体实现的特质

  • trait可以带具体实现的方法,继承这个trait也叫做"混入"了这个trait
  • 重写具体方法时加上override关键字

10.4 带有特质的对象

  • new一个对象的时候可以加上trait
trait Logged {
    def log(msg: String) { } // 一个空的实现
}
class SavingAccount extends Account with Logged {
    def withDraw(...) {
        ...
        log("xxx") // 混入Logged的log方法 但目前还是空的实现
    }
}
trait ConsoleLogger extends Logged { 
    override def log(msg: String ) {...} // 有一个更好的实现
}

val acct = new SavingAccount with ConsoleLogger // 在具体对象中混入更好的实现,不再是空实现
val acct2 = new SavingAccount with FileLogger

10.5 叠加在一起的特质

  • 从最后一个trait开始调用
  • traitsuper.方法含义与类的super.方法不一样!!调用的是trait层级中的下一个trait。下一个意思:根据trait的with顺序决定!!
  • 也可以具体制定哪个super的方法:super[ConsoleLogger].log(...) 但只能是直接超类型

10.6 重写抽象方法

  • 问题:万一super.方法所对应的那个trait方法是抽象方法怎么办?
  • 解决方法:重写的方法加上abstract关键字
    abstract override def log(msg: String) { super.log(...) }

10.7 具体方法中调用抽象方法

  • 在同一个trait中,具体方法里可以调用抽象方法和抽象字段,在被继承的时候再混入相应的具体方法和具体字段
trait Logger {
    def log(msg: String)
    def info(msg: String) { log("INFO: " + msg) } // 具体方法里调用了抽象方法
    ...
}

10.8 特质中的字段

  • trait的字段可具体可抽象,给出初始值就是具体的
  • 实现该trait的子类,这些trait的字段是被简单加到子类中的,不是被继承的
  • 不能说是被继承的原因:一个类只能extends一个超类,因此来自trait的字段只能是作为子类的其中一个字段
  • 抽象字段必须在子类中被重写
val acct = new SavingsAccount with ConsoleLogger with ShortLogger {
    val maxLength = 20 // 在ShortLogger里的maxLength是抽象的,所以在被with的时候需要具体化
}

10.10 构造器执行顺序

  • trait构造器 与类构造器类似 由trait体语句构成
  • 构造器执行顺序:超类->左边的trait->右边的trait->子类
  • trait构造器内部执行顺序:先执行父trait构造器(多个特质共有一个父的情况只执行一次构造)
    e.g. class SavingsAccount extends Account with FileLogger with ShortLogger
    构造器执行顺序:
    Account(超类)->Logger(FileLogger的父trait)->FileLogger(最左边trait)->ShortLogger(下一个trait)->SavingsAccount(子类)

10.11 初始化抽象字段

  • trait和类的区别:trait不能有构造器参数
  • 用abstract val代替trait构造器参数
  • 陷阱:可能在初始化抽象字段时,由于构造器执行顺序问题导致空指针异常
  • 解决方法:对应用到抽象字段的val加上lazy关键字

10.12 trait extends class

  • trait可以extends trait也可以extends class
trait T1 extends C1 {...} // T1扩展了类C1
class C2 extends T1 {...} // C1自动变成C2的父类!!
class C3 extends C0 with T1 {...} // 条件:C1是C0的父类!!

10.13 self type机制

  • 问题:如何从编译机制上保证C0是C1的子类?或者更一般地,如何保证T1只能被某个父类(C1)的子类所扩展?
  • 解决方案:在T1的定义里加入self type(自身类型)指定C1
trait T1 {
    this: C1 =>
    ...
} // 指定了T1只能混入C1的子类
  • 还可以指定T1只能混入【带有特定方法】的类,这叫做structural type(结构类型)
trait T1 {
    this: { def getMsg(): String } =>
    ...
} // 指定了T1只能混入带有getMsg方法的类

你可能感兴趣的:(Scala学习笔记 A2/L1篇 - 特质Traits)